<commit_before>"use strict";

const debug = require("debug")("controller");
const moment = require("moment");

const EventEmitter = require("events");

class Controller extends EventEmitter {
  constructor(param) {
    super();
    const {
      parallel,
      limit,
      errorlimit,
      errhandler,
      collect
    } = param;
    this.sleeper = 0;
    this.open = new Map();
    this.running = 0;
    this.limit = limit || 999999999;
    this.parallel = parallel || 20;
    this.collect = collect;
    if ( this.collect === undefined ){
      this.collect = true;
    }
    this.collector = [];
    this.errcollector = [];
    this.errorlimit = errorlimit || 1;
    this.errhandler = errhandler || ((err) => err);
  }

  startOne(fun, c) { //start function with object and supply event emit after end
    const temp = this.running++;

    if (temp >= this.limit) {
      this.done = true;
    }

    this.open.set(temp, c); //store running object
    return fun(c)
    .catch((err) => this.emit("arbeiten", err, null, temp))
      .then((res) => this.emit("arbeiten", null, res, temp)) // emit result
      ; // emit error

  }

  *
  startAll(webber, fun) { //start  function with an iterator n times
    this.sleepval = null;
    for (const c of webber) {
      yield this.startOne(fun, c);

    }
  }

  *
  waiter(starttim) { //block with a sleep function, if starttime is not yet reached
    const timeouter = (p) => new Promise((resolve) => setTimeout(resolve, p));

    if ((this.sleepval = (starttim - moment.now("X"))) > 0) {
      this.sleeper++;
      yield timeouter(this.sleepval)
        .then(() => this.emit("arbeiten")); // after sleeping emit event
    }

  }

  /* run controlled by a generator function, which supplies the promises*/

  waiterEnd(err,obj,pos) {

    //debug("hier da", obj,err,pos);
  if (err){
        debug("caughterr %d", this.errcollector.length, err);
        //store error

        if (this.errcollector.push(err.err) >= this.errorlimit) {
          //stop working, too many errors, but wait until finished
          this.done = true;
        }
  //      return this.errhandler(err); //repeat
      }
        if (pos !== undefined ) {
          //     debug("caught",  obj);
          if ( obj  && this.collect) {
            this.collector[pos] = obj; //store endresult in order of start like Promise.all
          }

          this.open.delete(pos); //remove from map of running
        } else { // no work, only sleep function
          this.sleeper--;
        }

        //check for endcondition of the run

        if (this.sleeper === 0 && this.open.size === 0 && this.done) {
          this.removeAllListeners("newrun");
          if (this.errcollector.length >= this.errorlimit) { //throw with error
            debug("finished with error %d", this.errcollector.length);
            this.emit("ende", this.errcollector );
            /*
            throw {
              errors: this.errcollector,
              position: this.running
            };
            */
          } else { // return the array of results
            debug("finished with gen", this.collector.length);
            //return Promise.resolve(this.collector);
            this.emit("ende", null,this.collector );
          }

        } else if (this.done) {
          debug("warten auf ende");
        //  return this.waiterEnd();

        } else { // repeat if end conditition is not reached
          /*          if (iter === this.running) {
                    debug("no new started", iter);
                  }
                  */
          if (this.emit("newrun")) {
          //  return this.waiterEnd(); //wait for new results
          } else {
            debug("error");
          }

        }
      }

  run(startgen) {
    while (!this.done && this.sleeper === 0 && this.open.size < this.parallel) {
      const done = startgen.next();
      debug("starting %d %j %d %d", this.open.size, done, this.done, this.running);
      if (!this.done) {
        this.done = done.done;
      }

    }
  }

  runner(startgen) {
    // wait for finished promise,
    // catch the promise after run and do end handling of the promise

    // fetch promises from generator with limited parallelism

    this.done = false;
    this.on("newrun", this.run.bind(this, startgen));
    this.on("arbeiten",this.waiterEnd.bind(this) );
    this.emit("newrun"); //start the run

    return new Promise((resolve, reject) => this.once("ende", (err, result) => err ? reject({
      errors: err,
      position: this.running
    }) :
            resolve(result)));

  }

  tester(x) { // supply test function
    return new Promise((resolve, reject) => setTimeout(() => {
      if (Math.random() < 0.00) {
        return reject({
          message: "kaputti" + this.running
        });
      } else {
        return resolve(x);
      }
    }, Math.random() * 5 + 1));
  }

  statistik() { //current statistics of  run
    return {
      limit: this.limit,
      parallel: this.parallel,
      errorlimit: this.errorlimit,
      notReady: this.running - this.collector.length, //number still in work
      openrun: [...this.open.keys()], // ids which are still running (oldest first)
      finished: this.collector.length, //number of finished
      errors: this.errcollector.length, //number of errors
      lastStarted: this.running, //last id which was started
      sleeps: this.sleeper, //number of sleep functions which are blocking
      done: this.done
        //cpu: this.startUsage=process.cpuUsage(this.startUsage)
    };

  }
}

module.exports = function factory(options) {

  return new Controller(options);
};
<commit_msg>Update controller.js<commit_after>"use strict";

const debug = require("debug")("controller");
const moment = require("moment");

const EventEmitter = require("events");

class Controller extends EventEmitter {
  constructor(param) {
    super();
    const {
      parallel,
      limit,
      errorlimit,
      errhandler,
      collect
    } = param;
    this.sleeper = 0;
    this.open = new Map();
    this.running = 0;
    this.limit = limit || 999999999;
    this.parallel = parallel || 20;
    this.collect = collect;
    if ( this.collect === undefined ){
      this.collect = true;
    }
    this.collector = [];
    this.errcollector = [];
    this.errorlimit = errorlimit || 1;
    this.errhandler = errhandler || ((err) => err);
  }

  startOne(fun, c) { //start function with object and supply event emit after end
    const temp = this.running++;

    if (temp >= this.limit) {
      this.done = true;
    }

    this.open.set(temp, c); //store running object
    return fun(c)
    .catch((err) => this.emit("arbeiten", err, null, temp))
      .then((res) => this.emit("arbeiten", null, res, temp)) // emit result
      ; // emit error

  }

  *
  startAll(webber, fun) { //start  function with an iterator n times
    this.sleepval = null;
    for (const c of webber) {
      yield this.startOne(fun, c);

    }
  }

  *
  waiter(starttim) { //block with a sleep function, if starttime is not yet reached
    const timeouter = (p) => new Promise((resolve) => setTimeout(resolve, p));

    if ((this.sleepval = (starttim - moment.now("X"))) > 0) {
      this.sleeper++;
      yield timeouter(this.sleepval)
        .then(() => this.emit("arbeiten")); // after sleeping emit event
    }

  }

  /* run controlled by a generator function, which supplies the promises*/

  waiterEnd(err,obj,pos) {

    //debug("hier da", obj,err,pos);
  if (err){
        debug("caughterr %d", this.errcollector.length, err);
        //store error

        if (this.errcollector.push(err) >= this.errorlimit) {
          //stop working, too many errors, but wait until finished
          this.done = true;
        }
  //      return this.errhandler(err); //repeat
      }
        if (pos !== undefined ) {
          //     debug("caught",  obj);
          if ( obj  && this.collect) {
            this.collector[pos] = obj; //store endresult in order of start like Promise.all
          }

          this.open.delete(pos); //remove from map of running
        } else { // no work, only sleep function
          this.sleeper--;
        }

        //check for endcondition of the run

        if (this.sleeper === 0 && this.open.size === 0 && this.done) {
          this.removeAllListeners("newrun");
          if (this.errcollector.length >= this.errorlimit) { //throw with error
            debug("finished with error %d", this.errcollector.length);
            this.emit("ende", this.errcollector );
            /*
            throw {
              errors: this.errcollector,
              position: this.running
            };
            */
          } else { // return the array of results
            debug("finished with gen", this.collector.length);
            //return Promise.resolve(this.collector);
            this.emit("ende", null,this.collector );
          }

        } else if (this.done) {
          debug("warten auf ende");
        //  return this.waiterEnd();

        } else { // repeat if end conditition is not reached
          /*          if (iter === this.running) {
                    debug("no new started", iter);
                  }
                  */
          if (this.emit("newrun")) {
          //  return this.waiterEnd(); //wait for new results
          } else {
            debug("error");
          }

        }
      }

  run(startgen) {
    while (!this.done && this.sleeper === 0 && this.open.size < this.parallel) {
      const done = startgen.next();
      debug("starting %d %j %d %d", this.open.size, done, this.done, this.running);
      if (!this.done) {
        this.done = done.done;
      }

    }
  }

  runner(startgen) {
    // wait for finished promise,
    // catch the promise after run and do end handling of the promise

    // fetch promises from generator with limited parallelism

    this.done = false;
    this.on("newrun", this.run.bind(this, startgen));
    this.on("arbeiten",this.waiterEnd.bind(this) );
    this.emit("newrun"); //start the run

    return new Promise((resolve, reject) => this.once("ende", (err, result) => err ? reject(err) :
            resolve(result)));

  }

  tester(x) { // supply test function
    return new Promise((resolve, reject) => setTimeout(() => {
      if (Math.random() < 0.00) {
        return reject({
          message: "kaputti" + this.running
        });
      } else {
        return resolve(x);
      }
    }, Math.random() * 5 + 1));
  }

  statistik() { //current statistics of  run
    return {
      limit: this.limit,
      parallel: this.parallel,
      errorlimit: this.errorlimit,
      notReady: this.running - this.collector.length, //number still in work
      openrun: [...this.open.keys()], // ids which are still running (oldest first)
      finished: this.collector.length, //number of finished
      errors: this.errcollector.length, //number of errors
      lastStarted: this.running, //last id which was started
      sleeps: this.sleeper, //number of sleep functions which are blocking
      done: this.done
        //cpu: this.startUsage=process.cpuUsage(this.startUsage)
    };

  }
}

module.exports = function factory(options) {

  return new Controller(options);
};
<|endoftext|>


<commit_before>const handlebars = require('handlebars');
const changelogHelper = require('./changelog');
const showdown = require('showdown');

const converter = new showdown.Converter();
converter.setFlavor('github');

module.exports = {
  ensurePr,
  checkAutoMerge,
};

// Ensures that PR exists with matching title/body
async function ensurePr(prConfig) {
  const config = { ...prConfig };

  logger.trace({ config }, 'ensurePr');
  // If there is a group, it will use the config of the first upgrade in the array
  const { branchName, upgrades } = config;
  config.upgrades = [];
  const branchStatus = await platform.getBranchStatus(
    branchName,
    config.requiredStatusChecks
  );

  // Only create a PR if a branch automerge has failed
  if (config.automerge === true && config.automergeType.startsWith('branch')) {
    logger.debug(
      `Branch is configured for branch automerge, branchStatus is: ${
        branchStatus
      }`
    );
    if (config.forcePr || branchStatus === 'failure') {
      logger.debug(`Branch tests failed, so will create PR`);
    } else {
      return null;
    }
  }
  if (config.prCreation === 'status-success') {
    logger.debug('Checking branch combined status');
    if (branchStatus !== 'success') {
      logger.debug(`Branch status is "${branchStatus}" - not creating PR`);
      return null;
    }
    logger.debug('Branch status success');
  } else if (config.prCreation === 'not-pending') {
    logger.debug('Checking branch combined status');
    if (branchStatus === 'pending' || branchStatus === 'running') {
      logger.debug(`Branch status is "${branchStatus}" - checking timeout`);
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours < config.prNotPendingHours) {
        logger.debug(
          `Branch is ${elapsedHours} hours old - skipping PR creation`
        );
        return null;
      }
      logger.debug(
        `prNotPendingHours=${
          config.prNotPendingHours
        } threshold hit - creating PR`
      );
    }
    logger.debug('Branch status success');
  }

  const processedUpgrades = [];

  const issueRe = /([\s(])#(\d+)([)\s]?)/g;

  // Get changelog and then generate template strings
  for (const upgrade of upgrades) {
    const upgradeKey = `${upgrade.depName}-${upgrade.changeLogFromVersion}-${
      upgrade.changeLogToVersion
    }`;
    if (processedUpgrades.indexOf(upgradeKey) !== -1) {
      continue; // eslint-disable-line no-continue
    }
    processedUpgrades.push(upgradeKey);

    const logJSON = await changelogHelper.getChangeLogJSON(
      upgrade.depName,
      upgrade.changeLogFromVersion,
      upgrade.changeLogToVersion
    );
    // Store changelog markdown for backwards compatibility
    if (logJSON) {
      upgrade.githubName = logJSON.project.github;
      upgrade.releases = [];
      logJSON.versions.forEach(version => {
        const release = { ...version };
        release.date = version.date.toISOString().slice(0, 10);
        release.commits = [];
        if (release.changes) {
          release.changes.forEach(change => {
            const commit = { ...change };
            delete commit.date;
            commit.shortSha = change.sha.slice(0, 7);
            commit.url = `${logJSON.project.repository}/commit/${change.sha}`;
            if (change.message) {
              [commit.message] = change.message.split('\n');
              if (!config.isGitHub || config.privateRepo === true) {
                commit.message = commit.message.replace(
                  issueRe,
                  `$1[#$2](${upgrade.repositoryUrl}/issues/$2)$3`
                );
              }
            }
            release.commits.push(commit);
          });
        }
        upgrade.releases.push(release);
      });
    }
    config.upgrades.push(upgrade);
  }

  // Update the config object
  Object.assign(config, upgrades[0]);
  if (config.errors && config.errors.length) {
    config.hasErrors = true;
  }
  if (config.warnings && config.warnings.length) {
    config.hasWarnings = true;
  }

  const prTitle = handlebars.compile(config.prTitle)(config);
  let prBody = handlebars.compile(config.prBody)(config);
  // Put a zero width space after every @ symbol to prevent unintended hyperlinking
  prBody = prBody.replace('@', '@');
  // Public GitHub repos need links prevented - see #489
  prBody = prBody.replace(issueRe, '$1#$2$3');
  // convert escaped backticks back to `
  const backTickRe = /`([^/]*?)`/g;
  prBody = prBody.replace(backTickRe, '`$1`');
  // It would be nice to abstract this to the platform layer but made difficult due to our create/update check
  if (config.isGitLab) {
    // Convert to HTML using GitHub-flavoured markdown as it is more feature-rich than GitLab's flavour
    prBody = converter
      .makeHtml(prBody)
      .replace('
', '
') // See #954
      .replace(/Pull Request/g, 'Merge Request')
      .replace(/PR/g, 'MR');
  }

  try {
    // Check if existing PR exists
    const existingPr = await platform.getBranchPr(branchName);
    if (existingPr) {
      if (config.automerge && branchStatus === 'failure') {
        logger.debug(`Setting assignees and reviewers as status checks failed`);
        await addAssigneesReviewers(config, existingPr);
      }
      // Check if existing PR needs updating
      if (existingPr.title === prTitle && existingPr.body === prBody) {
        logger.info(`${existingPr.displayNumber} does not need updating`);
        return existingPr;
      }
      // PR must need updating
      await platform.updatePr(existingPr.number, prTitle, prBody);
      logger.info(`Updated ${existingPr.displayNumber}`);
      return existingPr;
    }
    logger.debug({ prTitle }, `Creating PR for branch ${branchName}`);
    let pr;
    try {
      pr = await platform.createPr(branchName, prTitle, prBody, config.labels);
    } catch (err) {
      logger.warn({ err }, `Failed to create PR - deleting branch`);
      await platform.deleteBranch(branchName);
      return null;
    }
    // Skip assign and review if automerging PR
    if (
      config.automerge &&
      config.automergeType === 'pr' &&
      branchStatus !== 'failure'
    ) {
      logger.debug(
        `Skipping assignees and reviewers as automerge=${config.automerge}`
      );
    } else {
      await addAssigneesReviewers(config, pr);
    }
    logger.info(`Created ${pr.displayNumber}`);
    return pr;
  } catch (err) {
    logger.error({ err }, 'Failed to ensure PR:', err);
  }
  return null;
}

async function addAssigneesReviewers(config, pr) {
  if (config.assignees.length > 0) {
    try {
      const assignees = config.assignees.map(
        assignee =>
          assignee.length && assignee[0] === '@' ? assignee.slice(1) : assignee
      );
      await platform.addAssignees(pr.number, assignees);
      logger.info({ assignees: config.assignees }, 'Added assignees');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add assignees'
      );
    }
  }
  if (config.reviewers.length > 0) {
    try {
      const reviewers = config.reviewers.map(
        reviewer =>
          reviewer.length && reviewer[0] === '@' ? reviewer.slice(1) : reviewer
      );
      await platform.addReviewers(pr.number, reviewers);
      logger.info({ reviewers: config.reviewers }, 'Added reviewers');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add reviewers'
      );
    }
  }
}

async function checkAutoMerge(pr, config) {
  logger.trace({ config }, 'checkAutoMerge');
  logger.debug(`Checking #${pr.number} for automerge`);
  if (config.automerge === true && config.automergeType === 'pr') {
    logger.info('PR is configured for automerge');
    // Return if PR not ready for automerge
    if (pr.mergeable !== true) {
      logger.info('PR is not mergeable');
      return false;
    }
    if (config.requiredStatusChecks && pr.mergeable_state === 'unstable') {
      logger.info('PR mergeable state is unstable');
      return false;
    }
    // Check branch status
    const branchStatus = await platform.getBranchStatus(
      pr.head.ref,
      config.requiredStatusChecks
    );
    logger.debug(`branchStatus=${branchStatus}`);
    if (branchStatus !== 'success') {
      logger.info('Branch status is not "success"');
      return false;
    }
    // Check if it's been touched
    if (!pr.canRebase) {
      logger.info('PR is ready for automerge but has been modified');
      return false;
    }
    // Let's merge this
    logger.info(`Automerging #${pr.number}`);
    return platform.mergePr(pr.number, config.branchName);
  }
  logger.debug('No automerge');
  return false;
}
<commit_msg>fix: remove release.date conversion (#1144)<commit_after>const handlebars = require('handlebars');
const changelogHelper = require('./changelog');
const showdown = require('showdown');

const converter = new showdown.Converter();
converter.setFlavor('github');

module.exports = {
  ensurePr,
  checkAutoMerge,
};

// Ensures that PR exists with matching title/body
async function ensurePr(prConfig) {
  const config = { ...prConfig };

  logger.trace({ config }, 'ensurePr');
  // If there is a group, it will use the config of the first upgrade in the array
  const { branchName, upgrades } = config;
  config.upgrades = [];
  const branchStatus = await platform.getBranchStatus(
    branchName,
    config.requiredStatusChecks
  );

  // Only create a PR if a branch automerge has failed
  if (config.automerge === true && config.automergeType.startsWith('branch')) {
    logger.debug(
      `Branch is configured for branch automerge, branchStatus is: ${
        branchStatus
      }`
    );
    if (config.forcePr || branchStatus === 'failure') {
      logger.debug(`Branch tests failed, so will create PR`);
    } else {
      return null;
    }
  }
  if (config.prCreation === 'status-success') {
    logger.debug('Checking branch combined status');
    if (branchStatus !== 'success') {
      logger.debug(`Branch status is "${branchStatus}" - not creating PR`);
      return null;
    }
    logger.debug('Branch status success');
  } else if (config.prCreation === 'not-pending') {
    logger.debug('Checking branch combined status');
    if (branchStatus === 'pending' || branchStatus === 'running') {
      logger.debug(`Branch status is "${branchStatus}" - checking timeout`);
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours < config.prNotPendingHours) {
        logger.debug(
          `Branch is ${elapsedHours} hours old - skipping PR creation`
        );
        return null;
      }
      logger.debug(
        `prNotPendingHours=${
          config.prNotPendingHours
        } threshold hit - creating PR`
      );
    }
    logger.debug('Branch status success');
  }

  const processedUpgrades = [];

  const issueRe = /([\s(])#(\d+)([)\s]?)/g;

  // Get changelog and then generate template strings
  for (const upgrade of upgrades) {
    const upgradeKey = `${upgrade.depName}-${upgrade.changeLogFromVersion}-${
      upgrade.changeLogToVersion
    }`;
    if (processedUpgrades.indexOf(upgradeKey) !== -1) {
      continue; // eslint-disable-line no-continue
    }
    processedUpgrades.push(upgradeKey);

    const logJSON = await changelogHelper.getChangeLogJSON(
      upgrade.depName,
      upgrade.changeLogFromVersion,
      upgrade.changeLogToVersion
    );
    // Store changelog markdown for backwards compatibility
    if (logJSON) {
      upgrade.githubName = logJSON.project.github;
      upgrade.releases = [];
      logJSON.versions.forEach(version => {
        const release = { ...version };
        release.commits = [];
        if (release.changes) {
          release.changes.forEach(change => {
            const commit = { ...change };
            delete commit.date;
            commit.shortSha = change.sha.slice(0, 7);
            commit.url = `${logJSON.project.repository}/commit/${change.sha}`;
            if (change.message) {
              [commit.message] = change.message.split('\n');
              if (!config.isGitHub || config.privateRepo === true) {
                commit.message = commit.message.replace(
                  issueRe,
                  `$1[#$2](${upgrade.repositoryUrl}/issues/$2)$3`
                );
              }
            }
            release.commits.push(commit);
          });
        }
        upgrade.releases.push(release);
      });
    }
    config.upgrades.push(upgrade);
  }

  // Update the config object
  Object.assign(config, upgrades[0]);
  if (config.errors && config.errors.length) {
    config.hasErrors = true;
  }
  if (config.warnings && config.warnings.length) {
    config.hasWarnings = true;
  }

  const prTitle = handlebars.compile(config.prTitle)(config);
  let prBody = handlebars.compile(config.prBody)(config);
  // Put a zero width space after every @ symbol to prevent unintended hyperlinking
  prBody = prBody.replace('@', '@');
  // Public GitHub repos need links prevented - see #489
  prBody = prBody.replace(issueRe, '$1#$2$3');
  // convert escaped backticks back to `
  const backTickRe = /`([^/]*?)`/g;
  prBody = prBody.replace(backTickRe, '`$1`');
  // It would be nice to abstract this to the platform layer but made difficult due to our create/update check
  if (config.isGitLab) {
    // Convert to HTML using GitHub-flavoured markdown as it is more feature-rich than GitLab's flavour
    prBody = converter
      .makeHtml(prBody)
      .replace('
', '
') // See #954
      .replace(/Pull Request/g, 'Merge Request')
      .replace(/PR/g, 'MR');
  }

  try {
    // Check if existing PR exists
    const existingPr = await platform.getBranchPr(branchName);
    if (existingPr) {
      if (config.automerge && branchStatus === 'failure') {
        logger.debug(`Setting assignees and reviewers as status checks failed`);
        await addAssigneesReviewers(config, existingPr);
      }
      // Check if existing PR needs updating
      if (existingPr.title === prTitle && existingPr.body === prBody) {
        logger.info(`${existingPr.displayNumber} does not need updating`);
        return existingPr;
      }
      // PR must need updating
      await platform.updatePr(existingPr.number, prTitle, prBody);
      logger.info(`Updated ${existingPr.displayNumber}`);
      return existingPr;
    }
    logger.debug({ prTitle }, `Creating PR for branch ${branchName}`);
    let pr;
    try {
      pr = await platform.createPr(branchName, prTitle, prBody, config.labels);
    } catch (err) {
      logger.warn({ err }, `Failed to create PR - deleting branch`);
      await platform.deleteBranch(branchName);
      return null;
    }
    // Skip assign and review if automerging PR
    if (
      config.automerge &&
      config.automergeType === 'pr' &&
      branchStatus !== 'failure'
    ) {
      logger.debug(
        `Skipping assignees and reviewers as automerge=${config.automerge}`
      );
    } else {
      await addAssigneesReviewers(config, pr);
    }
    logger.info(`Created ${pr.displayNumber}`);
    return pr;
  } catch (err) {
    logger.error({ err }, 'Failed to ensure PR:', err);
  }
  return null;
}

async function addAssigneesReviewers(config, pr) {
  if (config.assignees.length > 0) {
    try {
      const assignees = config.assignees.map(
        assignee =>
          assignee.length && assignee[0] === '@' ? assignee.slice(1) : assignee
      );
      await platform.addAssignees(pr.number, assignees);
      logger.info({ assignees: config.assignees }, 'Added assignees');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add assignees'
      );
    }
  }
  if (config.reviewers.length > 0) {
    try {
      const reviewers = config.reviewers.map(
        reviewer =>
          reviewer.length && reviewer[0] === '@' ? reviewer.slice(1) : reviewer
      );
      await platform.addReviewers(pr.number, reviewers);
      logger.info({ reviewers: config.reviewers }, 'Added reviewers');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add reviewers'
      );
    }
  }
}

async function checkAutoMerge(pr, config) {
  logger.trace({ config }, 'checkAutoMerge');
  logger.debug(`Checking #${pr.number} for automerge`);
  if (config.automerge === true && config.automergeType === 'pr') {
    logger.info('PR is configured for automerge');
    // Return if PR not ready for automerge
    if (pr.mergeable !== true) {
      logger.info('PR is not mergeable');
      return false;
    }
    if (config.requiredStatusChecks && pr.mergeable_state === 'unstable') {
      logger.info('PR mergeable state is unstable');
      return false;
    }
    // Check branch status
    const branchStatus = await platform.getBranchStatus(
      pr.head.ref,
      config.requiredStatusChecks
    );
    logger.debug(`branchStatus=${branchStatus}`);
    if (branchStatus !== 'success') {
      logger.info('Branch status is not "success"');
      return false;
    }
    // Check if it's been touched
    if (!pr.canRebase) {
      logger.info('PR is ready for automerge but has been modified');
      return false;
    }
    // Let's merge this
    logger.info(`Automerging #${pr.number}`);
    return platform.mergePr(pr.number, config.branchName);
  }
  logger.debug('No automerge');
  return false;
}
<|endoftext|>


<commit_before>var Promise         = require('bluebird')
  , _               = require('lodash')
  , os              = require('os')
  , url             = require('url')
  , uuid            = require('node-uuid')
  , config          = require('config')
  ;

function Agent(PoolFactory) {
  this.pool = PoolFactory(this);

  if (!this.pool) {
    throw new Error('"type" param not defined or recognized');
  }

  this.group = config.get('group.id');
  this.id = uuid.v4();

  if (!this.group) {
    throw new Error('group id is required for the agent to connect to the relay. make sure that your group id is unique.');
  }

  var mem_mb = os.totalmem() / 1024 / 1024;
  var mem_os = Math.max(config.get('kernal.mem-min'), config.get('kernal.mem-ratio') * mem_mb);
  this.capacity = Math.floor((mem_mb - mem_os) /  config.get('worker.mem'))
}

Agent.prototype.initialize = function(cb) {
  Promise
    .promisify(this.pool.kill_workers, { context : this.pool })()
    .nodeify(cb)
    ;
};

Agent.prototype.listen = function() {
  var me = this;

  console.log('connecting to:', config.get('relay.host'));

  this.socket = require('socket.io-client')(url.format({
      protocol  : config.get('relay.protocol')
    , hostname  : config.get('relay.host')
    , port      : config.get('relay.port')
  }));

  this.socket.on('connect', function(){
    me.start_heartbeat();
    console.log('connected');
  });

  this.socket.on('disconnect', function(){
    me.end_heartbeat();
    console.log('disconnected');
  });

  this.socket.on('/run', function(task, cb){
    Promise
      .try(function(){
        return me.pool.create(task);
      })
      .then(function(worker){
        return Promise.promisify(worker.run, { context : worker })();
      })
      .then(function(){ return undefined; /*mask response*/ })
      .catch(function(err){
        // todo [akamel] abstract to common module [also used in relay]
        throw {
            type    : _.isError(err)? 'exception' : 'notification'
          , error   : err.message
          // todo [akamel] should we expose this? its OSS anyway
          , stack   : err.stack
          , target  : 'taskmill-core-agent'
        };
      })
      .nodeify(cb)
      ;
  });

  this.socket.on('/SIGKILL', function(task, cb){
    Promise
      .try(function(){
        return task.id;
      })
      .then(function(id){
        me.pool.kill(id);
      })
      .then(function(){ return undefined; /*mask response*/ })
      .catch(function(err){
        // todo [akamel] abstract to common module [also used in relay]
        throw {
            type    : _.isError(err)? 'exception' : 'notification'
          , error   : err.message
          // todo [akamel] should we expose this? its OSS anyway
          , stack   : err.stack
          , target  : 'taskmill-core-agent'
        };
      })
      .nodeify(cb)
      ;
  });
};

Agent.prototype.start_heartbeat = function() {
  this.end_heartbeat();

  var me = this;

  function ping(socket) {
    var data = {
        name                : os.hostname()
      , id                  : me.id
      , group               : me.group
      , uptime              : process.uptime()
      , capacity            : me.capacity
      , workers             : me.pool.info()
    };

    socket.emit('ping', data);
  }

  this.heartbeat_timer_token = setInterval(ping, 60 * 1000, this.socket);
  ping(this.socket);
};

Agent.prototype.end_heartbeat = function() {
  if (this.heartbeat_timer_token) {
    clearInterval(this.heartbeat_timer_token);
  }
};

module.exports = Agent;<commit_msg>refactor agent ping method<commit_after>var Promise         = require('bluebird')
  , _               = require('lodash')
  , os              = require('os')
  , url             = require('url')
  , uuid            = require('node-uuid')
  , config          = require('config')
  ;

function Agent(PoolFactory) {
  this.pool = PoolFactory(this);

  if (!this.pool) {
    throw new Error('"type" param not defined or recognized');
  }

  this.group = config.get('group.id');
  this.id = uuid.v4();

  if (!this.group) {
    throw new Error('group id is required for the agent to connect to the relay. make sure that your group id is unique.');
  }

  var mem_mb = os.totalmem() / 1024 / 1024;
  var mem_os = Math.max(config.get('kernal.mem-min'), config.get('kernal.mem-ratio') * mem_mb);
  this.capacity = Math.floor((mem_mb - mem_os) /  config.get('worker.mem'))
}

Agent.prototype.initialize = function(cb) {
  Promise
    .promisify(this.pool.kill_workers, { context : this.pool })()
    .nodeify(cb)
    ;
};

Agent.prototype.listen = function() {
  var me = this;

  console.log('connecting to:', config.get('relay.host'));

  this.socket = require('socket.io-client')(url.format({
      protocol  : config.get('relay.protocol')
    , hostname  : config.get('relay.host')
    , port      : config.get('relay.port')
  }));

  this.socket.on('connect', function(){
    me.start_heartbeat();
    console.log('connected');
  });

  this.socket.on('disconnect', function(){
    me.end_heartbeat();
    console.log('disconnected');
  });

  this.socket.on('/run', function(task, cb){
    Promise
      .try(function(){
        return me.pool.create(task);
      })
      .then(function(worker){
        return Promise.promisify(worker.run, { context : worker })();
      })
      .then(function(){ return undefined; /*mask response*/ })
      .catch(function(err){
        // todo [akamel] abstract to common module [also used in relay]
        throw {
            type    : _.isError(err)? 'exception' : 'notification'
          , error   : err.message
          // todo [akamel] should we expose this? its OSS anyway
          , stack   : err.stack
          , target  : 'taskmill-core-agent'
        };
      })
      .nodeify(cb)
      ;
  });

  this.socket.on('/SIGKILL', function(task, cb){
    Promise
      .try(function(){
        return task.id;
      })
      .then(function(id){
        me.pool.kill(id);
      })
      .then(function(){ return undefined; /*mask response*/ })
      .catch(function(err){
        // todo [akamel] abstract to common module [also used in relay]
        throw {
            type    : _.isError(err)? 'exception' : 'notification'
          , error   : err.message
          // todo [akamel] should we expose this? its OSS anyway
          , stack   : err.stack
          , target  : 'taskmill-core-agent'
        };
      })
      .nodeify(cb)
      ;
  });
};

Agent.prototype.start_heartbeat = function() {
  this.end_heartbeat();

  var ping = () => {
    var data = {
        name                : os.hostname()
      , id                  : this.id
      , group               : this.group
      , uptime              : process.uptime()
      , capacity            : this.capacity
      , workers             : this.pool.info()
    };

    this.socket.emit('ping', data);
  };

  this.heartbeat_timer_token = setInterval(ping, 60 * 1000);
  ping();
};

Agent.prototype.end_heartbeat = function() {
  if (this.heartbeat_timer_token) {
    clearInterval(this.heartbeat_timer_token);
  }
};

module.exports = Agent;<|endoftext|>


<commit_before>const changelogHelper = require('./changelog');
const { getPrBody } = require('./pr-body');

module.exports = {
  ensurePr,
  checkAutoMerge,
};
// Ensures that PR exists with matching title/body
async function ensurePr(prConfig) {
  const config = { ...prConfig };

  logger.trace({ config }, 'ensurePr');
  // If there is a group, it will use the config of the first upgrade in the array
  const { branchName, prTitle, upgrades } = config;
  // Check if existing PR exists
  const existingPr = await platform.getBranchPr(branchName);
  if (existingPr) {
    logger.debug('Found existing PR');
  }
  config.upgrades = [];

  if (config.lockFileErrors && config.lockFileErrors.length) {
    logger.debug('Forcing PR because of lock file errors');
    config.forcePr = true;
  }

  let branchStatus;
  async function getBranchStatus() {
    if (!branchStatus) {
      branchStatus = await platform.getBranchStatus(
        branchName,
        config.requiredStatusChecks
      );
    }
    return branchStatus;
  }

  // Only create a PR if a branch automerge has failed
  if (
    config.automerge === true &&
    config.automergeType.startsWith('branch') &&
    !config.forcePr
  ) {
    logger.debug(
      `Branch is configured for branch automerge, branch status) is: ${await getBranchStatus()}`
    );
    if (
      (await getBranchStatus()) === 'pending' ||
      (await getBranchStatus()) === 'running'
    ) {
      logger.debug('Checking how long this branch has been pending');
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours >= config.prNotPendingHours) {
        logger.info('Branch exceeds prNotPending hours - forcing PR creation');
        config.forcePr = true;
      }
    }
    if (config.forcePr || (await getBranchStatus()) === 'failure') {
      logger.debug(`Branch tests failed, so will create PR`);
    } else {
      return null;
    }
  }
  if (config.prCreation === 'status-success') {
    logger.debug('Checking branch combined status');
    if ((await getBranchStatus()) !== 'success') {
      logger.debug(
        `Branch status is "${await getBranchStatus()}" - not creating PR`
      );
      return null;
    }
    logger.debug('Branch status success');
  } else if (
    config.prCreation === 'not-pending' &&
    !existingPr &&
    !config.forcePr
  ) {
    logger.debug('Checking branch combined status');
    if (
      (await getBranchStatus()) === 'pending' ||
      (await getBranchStatus()) === 'running'
    ) {
      logger.debug(
        `Branch status is "${await getBranchStatus()}" - checking timeout`
      );
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours < config.prNotPendingHours) {
        logger.debug(
          `Branch is ${elapsedHours} hours old - skipping PR creation`
        );
        return null;
      }
      logger.debug(
        `prNotPendingHours=${
          config.prNotPendingHours
        } threshold hit - creating PR`
      );
    }
    logger.debug('Branch status success');
  }

  const processedUpgrades = [];
  const commitRepos = [];

  // Get changelog and then generate template strings
  for (const upgrade of upgrades) {
    const upgradeKey = `${upgrade.depName}-${
      upgrade.manager
    }-${upgrade.fromVersion || upgrade.currentValue}-${upgrade.toVersion}`;
    if (processedUpgrades.includes(upgradeKey)) {
      continue; // eslint-disable-line no-continue
    }
    processedUpgrades.push(upgradeKey);
    upgrade.hasUrls = !!(upgrade.repositoryUrl || upgrade.homepage);

    const logJSON = await changelogHelper.getChangeLogJSON({
      manager: upgrade.manager,
      versionScheme: upgrade.versionScheme,
      depType: upgrade.depType,
      depName: upgrade.depName,
      fromVersion: upgrade.fromVersion,
      toVersion: upgrade.toVersion,
      repositoryUrl: upgrade.repositoryUrl,
      releases: upgrade.releases,
    });

    if (logJSON) {
      upgrade.githubName = logJSON.project ? logJSON.project.github : undefined;
      upgrade.hasReleaseNotes = logJSON.hasReleaseNotes;
      upgrade.releases = [];
      if (upgrade.githubName && !commitRepos.includes(upgrade.githubName)) {
        commitRepos.push(upgrade.githubName);
        logJSON.versions.forEach(version => {
          const release = { ...version };
          upgrade.releases.push(release);
        });
      }
    }
    config.upgrades.push(upgrade);
  }

  // Update the config object
  Object.assign(config, upgrades[0]);
  config.hasReleaseNotes = config.upgrades.some(upg => upg.hasReleaseNotes);

  const releaseNoteRepos = [];
  for (const upgrade of config.upgrades) {
    if (releaseNoteRepos.includes(upgrade.repositoryUrl)) {
      logger.debug(
        { depName: upgrade.depName },
        'Removing duplicate release notes'
      );
      upgrade.hasReleaseNotes = false;
    } else {
      releaseNoteRepos.push(upgrade.repositoryUrl);
    }
  }

  const prBody = await getPrBody(config);

  try {
    if (existingPr) {
      logger.debug('Processing existing PR');
      // istanbul ignore if
      if (config.automerge && (await getBranchStatus()) === 'failure') {
        logger.debug(`Setting assignees and reviewers as status checks failed`);
        await addAssigneesReviewers(config, existingPr);
      }
      // Check if existing PR needs updating
      const reviewableIndex = existingPr.body.indexOf(
        '<!-- Reviewable:start -->'
      );
      let existingPrBody = existingPr.body;
      if (reviewableIndex > -1) {
        logger.debug('Stripping Reviewable content');
        existingPrBody = existingPrBody.slice(0, reviewableIndex);
      }
      existingPrBody = existingPrBody.trim();
      if (existingPr.title === prTitle && existingPrBody === prBody) {
        logger.debug(`${existingPr.displayNumber} does not need updating`);
        return existingPr;
      }
      // PR must need updating
      if (existingPr.title !== prTitle) {
        logger.info(
          {
            branchName,
            oldPrTitle: existingPr.title,
            newPrTitle: prTitle,
          },
          'PR title changed'
        );
      } else if (!config.committedFiles) {
        logger.debug(
          {
            prTitle,
            oldPrBody: existingPrBody,
            newPrBody: prBody,
          },
          'PR body changed'
        );
      }

      await platform.updatePr(existingPr.number, prTitle, prBody);
      logger.info(
        { committedFiles: config.committedFiles, pr: existingPr.number },
        `PR updated`
      );
      return existingPr;
    }
    logger.debug({ branch: branchName, prTitle }, `Creating PR`);
    // istanbul ignore if
    if (config.updateType === 'rollback') {
      logger.info('Creating Rollback PR');
    }
    let pr;
    try {
      pr = await platform.createPr(
        branchName,
        prTitle,
        prBody,
        config.labels,
        false,
        config.statusCheckVerify
      );
      logger.info({ branch: branchName, pr: pr.number }, 'PR created');
    } catch (err) {
      logger.warn({ err }, `Failed to create PR`);
      if (err.message === 'Validation Failed (422)') {
        logger.info({ branch: branchName }, 'Deleting invalid branch');
        await platform.deleteBranch(branchName);
      }
      // istanbul ignore if
      if (err.statusCode === 502) {
        logger.info(
          { branch: branchName },
          'Deleting branch due to server error'
        );
        await platform.deleteBranch(branchName);
      }
      return null;
    }
    if (config.branchAutomergeFailureMessage) {
      const subject = 'Branch automerge failure';
      let content =
        'This PR was configured for branch automerge, however this is not possible so it has been raised as a PR instead.';
      if (config.branchAutomergeFailureMessage === 'branch status error') {
        content += '\n___\n * Branch has one or more failed status checks';
      }
      logger.info('Adding branch automerge failure message to PR');
      platform.ensureComment(pr.number, subject, content);
    }
    // Skip assign and review if automerging PR
    if (config.automerge && (await getBranchStatus()) !== 'failure') {
      logger.debug(
        `Skipping assignees and reviewers as automerge=${config.automerge}`
      );
    } else {
      await addAssigneesReviewers(config, pr);
    }
    logger.info(`Created ${pr.displayNumber}`);
    return pr;
  } catch (err) {
    // istanbul ignore if
    if (
      err.message === 'rate-limit-exceeded' ||
      err.message === 'platform-failure'
    ) {
      logger.debug('Passing error up');
      throw err;
    }
    logger.error({ err }, 'Failed to ensure PR:', err);
  }
  return null;
}

async function addAssigneesReviewers(config, pr) {
  if (config.assignees.length > 0) {
    try {
      const assignees = config.assignees.map(
        assignee =>
          assignee.length && assignee[0] === '@' ? assignee.slice(1) : assignee
      );
      await platform.addAssignees(pr.number, assignees);
      logger.info({ assignees: config.assignees }, 'Added assignees');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add assignees'
      );
    }
  }
  if (config.reviewers.length > 0) {
    try {
      const reviewers = config.reviewers.map(
        reviewer =>
          reviewer.length && reviewer[0] === '@' ? reviewer.slice(1) : reviewer
      );
      await platform.addReviewers(pr.number, reviewers);
      logger.info({ reviewers: config.reviewers }, 'Added reviewers');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add reviewers'
      );
    }
  }
}

async function checkAutoMerge(pr, config) {
  logger.trace({ config }, 'checkAutoMerge');
  const {
    branchName,
    automerge,
    automergeType,
    automergeComment,
    requiredStatusChecks,
  } = config;
  logger.debug(
    { automerge, automergeType, automergeComment },
    `Checking #${pr.number} for automerge`
  );
  if (automerge) {
    logger.debug('PR is configured for automerge');
    // Return if PR not ready for automerge
    if (pr.isConflicted) {
      logger.info('PR is conflicted');
      logger.debug({ pr });
      return false;
    }
    if (requiredStatusChecks && pr.canMerge !== true) {
      logger.info('PR is not ready for merge');
      return false;
    }
    // Check branch status
    const branchStatus = await platform.getBranchStatus(
      pr.branchName,
      requiredStatusChecks
    );
    logger.debug(`branchStatus=${branchStatus}`);
    if (branchStatus !== 'success') {
      logger.debug('Branch status is not "success"');
      return false;
    }
    // Check if it's been touched
    if (!pr.canRebase) {
      logger.info('PR is ready for automerge but has been modified');
      return false;
    }
    if (automergeType === 'pr-comment') {
      logger.info(`Applying automerge comment: ${automergeComment}`);
      return platform.ensureComment(pr.number, null, automergeComment);
    }
    // Let's merge this
    logger.debug(`Automerging #${pr.number}`);
    const res = platform.mergePr(pr.number, branchName);
    logger.info({ pr: pr.number }, 'PR automerged');
    return res;
  }
  logger.debug('No automerge');
  return false;
}
<commit_msg>fix: await platform.ensureComment<commit_after>const changelogHelper = require('./changelog');
const { getPrBody } = require('./pr-body');

module.exports = {
  ensurePr,
  checkAutoMerge,
};
// Ensures that PR exists with matching title/body
async function ensurePr(prConfig) {
  const config = { ...prConfig };

  logger.trace({ config }, 'ensurePr');
  // If there is a group, it will use the config of the first upgrade in the array
  const { branchName, prTitle, upgrades } = config;
  // Check if existing PR exists
  const existingPr = await platform.getBranchPr(branchName);
  if (existingPr) {
    logger.debug('Found existing PR');
  }
  config.upgrades = [];

  if (config.lockFileErrors && config.lockFileErrors.length) {
    logger.debug('Forcing PR because of lock file errors');
    config.forcePr = true;
  }

  let branchStatus;
  async function getBranchStatus() {
    if (!branchStatus) {
      branchStatus = await platform.getBranchStatus(
        branchName,
        config.requiredStatusChecks
      );
    }
    return branchStatus;
  }

  // Only create a PR if a branch automerge has failed
  if (
    config.automerge === true &&
    config.automergeType.startsWith('branch') &&
    !config.forcePr
  ) {
    logger.debug(
      `Branch is configured for branch automerge, branch status) is: ${await getBranchStatus()}`
    );
    if (
      (await getBranchStatus()) === 'pending' ||
      (await getBranchStatus()) === 'running'
    ) {
      logger.debug('Checking how long this branch has been pending');
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours >= config.prNotPendingHours) {
        logger.info('Branch exceeds prNotPending hours - forcing PR creation');
        config.forcePr = true;
      }
    }
    if (config.forcePr || (await getBranchStatus()) === 'failure') {
      logger.debug(`Branch tests failed, so will create PR`);
    } else {
      return null;
    }
  }
  if (config.prCreation === 'status-success') {
    logger.debug('Checking branch combined status');
    if ((await getBranchStatus()) !== 'success') {
      logger.debug(
        `Branch status is "${await getBranchStatus()}" - not creating PR`
      );
      return null;
    }
    logger.debug('Branch status success');
  } else if (
    config.prCreation === 'not-pending' &&
    !existingPr &&
    !config.forcePr
  ) {
    logger.debug('Checking branch combined status');
    if (
      (await getBranchStatus()) === 'pending' ||
      (await getBranchStatus()) === 'running'
    ) {
      logger.debug(
        `Branch status is "${await getBranchStatus()}" - checking timeout`
      );
      const lastCommitTime = await platform.getBranchLastCommitTime(branchName);
      const currentTime = new Date();
      const millisecondsPerHour = 1000 * 60 * 60;
      const elapsedHours = Math.round(
        (currentTime.getTime() - lastCommitTime.getTime()) / millisecondsPerHour
      );
      if (elapsedHours < config.prNotPendingHours) {
        logger.debug(
          `Branch is ${elapsedHours} hours old - skipping PR creation`
        );
        return null;
      }
      logger.debug(
        `prNotPendingHours=${
          config.prNotPendingHours
        } threshold hit - creating PR`
      );
    }
    logger.debug('Branch status success');
  }

  const processedUpgrades = [];
  const commitRepos = [];

  // Get changelog and then generate template strings
  for (const upgrade of upgrades) {
    const upgradeKey = `${upgrade.depName}-${
      upgrade.manager
    }-${upgrade.fromVersion || upgrade.currentValue}-${upgrade.toVersion}`;
    if (processedUpgrades.includes(upgradeKey)) {
      continue; // eslint-disable-line no-continue
    }
    processedUpgrades.push(upgradeKey);
    upgrade.hasUrls = !!(upgrade.repositoryUrl || upgrade.homepage);

    const logJSON = await changelogHelper.getChangeLogJSON({
      manager: upgrade.manager,
      versionScheme: upgrade.versionScheme,
      depType: upgrade.depType,
      depName: upgrade.depName,
      fromVersion: upgrade.fromVersion,
      toVersion: upgrade.toVersion,
      repositoryUrl: upgrade.repositoryUrl,
      releases: upgrade.releases,
    });

    if (logJSON) {
      upgrade.githubName = logJSON.project ? logJSON.project.github : undefined;
      upgrade.hasReleaseNotes = logJSON.hasReleaseNotes;
      upgrade.releases = [];
      if (upgrade.githubName && !commitRepos.includes(upgrade.githubName)) {
        commitRepos.push(upgrade.githubName);
        logJSON.versions.forEach(version => {
          const release = { ...version };
          upgrade.releases.push(release);
        });
      }
    }
    config.upgrades.push(upgrade);
  }

  // Update the config object
  Object.assign(config, upgrades[0]);
  config.hasReleaseNotes = config.upgrades.some(upg => upg.hasReleaseNotes);

  const releaseNoteRepos = [];
  for (const upgrade of config.upgrades) {
    if (releaseNoteRepos.includes(upgrade.repositoryUrl)) {
      logger.debug(
        { depName: upgrade.depName },
        'Removing duplicate release notes'
      );
      upgrade.hasReleaseNotes = false;
    } else {
      releaseNoteRepos.push(upgrade.repositoryUrl);
    }
  }

  const prBody = await getPrBody(config);

  try {
    if (existingPr) {
      logger.debug('Processing existing PR');
      // istanbul ignore if
      if (config.automerge && (await getBranchStatus()) === 'failure') {
        logger.debug(`Setting assignees and reviewers as status checks failed`);
        await addAssigneesReviewers(config, existingPr);
      }
      // Check if existing PR needs updating
      const reviewableIndex = existingPr.body.indexOf(
        '<!-- Reviewable:start -->'
      );
      let existingPrBody = existingPr.body;
      if (reviewableIndex > -1) {
        logger.debug('Stripping Reviewable content');
        existingPrBody = existingPrBody.slice(0, reviewableIndex);
      }
      existingPrBody = existingPrBody.trim();
      if (existingPr.title === prTitle && existingPrBody === prBody) {
        logger.debug(`${existingPr.displayNumber} does not need updating`);
        return existingPr;
      }
      // PR must need updating
      if (existingPr.title !== prTitle) {
        logger.info(
          {
            branchName,
            oldPrTitle: existingPr.title,
            newPrTitle: prTitle,
          },
          'PR title changed'
        );
      } else if (!config.committedFiles) {
        logger.debug(
          {
            prTitle,
            oldPrBody: existingPrBody,
            newPrBody: prBody,
          },
          'PR body changed'
        );
      }

      await platform.updatePr(existingPr.number, prTitle, prBody);
      logger.info(
        { committedFiles: config.committedFiles, pr: existingPr.number },
        `PR updated`
      );
      return existingPr;
    }
    logger.debug({ branch: branchName, prTitle }, `Creating PR`);
    // istanbul ignore if
    if (config.updateType === 'rollback') {
      logger.info('Creating Rollback PR');
    }
    let pr;
    try {
      pr = await platform.createPr(
        branchName,
        prTitle,
        prBody,
        config.labels,
        false,
        config.statusCheckVerify
      );
      logger.info({ branch: branchName, pr: pr.number }, 'PR created');
    } catch (err) {
      logger.warn({ err }, `Failed to create PR`);
      if (err.message === 'Validation Failed (422)') {
        logger.info({ branch: branchName }, 'Deleting invalid branch');
        await platform.deleteBranch(branchName);
      }
      // istanbul ignore if
      if (err.statusCode === 502) {
        logger.info(
          { branch: branchName },
          'Deleting branch due to server error'
        );
        await platform.deleteBranch(branchName);
      }
      return null;
    }
    if (config.branchAutomergeFailureMessage) {
      const subject = 'Branch automerge failure';
      let content =
        'This PR was configured for branch automerge, however this is not possible so it has been raised as a PR instead.';
      if (config.branchAutomergeFailureMessage === 'branch status error') {
        content += '\n___\n * Branch has one or more failed status checks';
      }
      logger.info('Adding branch automerge failure message to PR');
      await platform.ensureComment(pr.number, subject, content);
    }
    // Skip assign and review if automerging PR
    if (config.automerge && (await getBranchStatus()) !== 'failure') {
      logger.debug(
        `Skipping assignees and reviewers as automerge=${config.automerge}`
      );
    } else {
      await addAssigneesReviewers(config, pr);
    }
    logger.info(`Created ${pr.displayNumber}`);
    return pr;
  } catch (err) {
    // istanbul ignore if
    if (
      err.message === 'rate-limit-exceeded' ||
      err.message === 'platform-failure'
    ) {
      logger.debug('Passing error up');
      throw err;
    }
    logger.error({ err }, 'Failed to ensure PR:', err);
  }
  return null;
}

async function addAssigneesReviewers(config, pr) {
  if (config.assignees.length > 0) {
    try {
      const assignees = config.assignees.map(
        assignee =>
          assignee.length && assignee[0] === '@' ? assignee.slice(1) : assignee
      );
      await platform.addAssignees(pr.number, assignees);
      logger.info({ assignees: config.assignees }, 'Added assignees');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add assignees'
      );
    }
  }
  if (config.reviewers.length > 0) {
    try {
      const reviewers = config.reviewers.map(
        reviewer =>
          reviewer.length && reviewer[0] === '@' ? reviewer.slice(1) : reviewer
      );
      await platform.addReviewers(pr.number, reviewers);
      logger.info({ reviewers: config.reviewers }, 'Added reviewers');
    } catch (err) {
      logger.info(
        { assignees: config.assignees, err },
        'Failed to add reviewers'
      );
    }
  }
}

async function checkAutoMerge(pr, config) {
  logger.trace({ config }, 'checkAutoMerge');
  const {
    branchName,
    automerge,
    automergeType,
    automergeComment,
    requiredStatusChecks,
  } = config;
  logger.debug(
    { automerge, automergeType, automergeComment },
    `Checking #${pr.number} for automerge`
  );
  if (automerge) {
    logger.debug('PR is configured for automerge');
    // Return if PR not ready for automerge
    if (pr.isConflicted) {
      logger.info('PR is conflicted');
      logger.debug({ pr });
      return false;
    }
    if (requiredStatusChecks && pr.canMerge !== true) {
      logger.info('PR is not ready for merge');
      return false;
    }
    // Check branch status
    const branchStatus = await platform.getBranchStatus(
      pr.branchName,
      requiredStatusChecks
    );
    logger.debug(`branchStatus=${branchStatus}`);
    if (branchStatus !== 'success') {
      logger.debug('Branch status is not "success"');
      return false;
    }
    // Check if it's been touched
    if (!pr.canRebase) {
      logger.info('PR is ready for automerge but has been modified');
      return false;
    }
    if (automergeType === 'pr-comment') {
      logger.info(`Applying automerge comment: ${automergeComment}`);
      return platform.ensureComment(pr.number, null, automergeComment);
    }
    // Let's merge this
    logger.debug(`Automerging #${pr.number}`);
    const res = platform.mergePr(pr.number, branchName);
    logger.info({ pr: pr.number }, 'PR automerged');
    return res;
  }
  logger.debug('No automerge');
  return false;
}
<|endoftext|>


<commit_before>var geohash = require('ngeohash');
var geolib = require('geolib');

var helper = require('./util/helper');

var query = require('./query');
var range = require('./range');

var convertUnitsFromMeters = helper.convertUnitsFromMeters;
var convertUnitsToMeters = helper.convertUnitsToMeters;
var buildObjectFromKeyedArray = helper.buildObjectFromKeyedArray;
var buildObjectFromStringArray = helper.buildObjectFromStringArray;

// emulated interface constructor

function EmulatedInterface(client) {
    this.client = client;
    console.log('GeoRedis using EMULATED interface...');
}

// Native Geo Commands

EmulatedInterface.prototype.geoadd = function(locationName, point, zSetName, callBack) {
    this.client.zadd(zSetName, geohash.encode_int(point.latitude, point.longitude, 52), locationName, callBack);
};

EmulatedInterface.prototype.geoadd_multi = function(locationSet, zSetName, callBack) {
    var argArray = [zSetName];
    var locationName;
    var location;

    for (locationName in locationSet) {
        location = locationSet[locationName];
        argArray.push(geohash.encode_int(location.latitude, location.longitude, 52), locationName);
    }

    this.client.zadd(argArray, callBack);
};

EmulatedInterface.prototype.geodist = function(locationA, locationB, units, zSetName, callBack) {

    var multi = this.client.multi();
    var pointA;
    var pointB;
    var distance;

    multi.zscore(zSetName, locationA);
    multi.zscore(zSetName, locationB);

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            pointA = geohashDecode(replies[0]);
            pointB = geohashDecode(replies[1]);

            distance = geolib.getDistance(pointA, pointB, 1);
            distance = convertUnitsFromMeters(units, distance);

            callBack(null, distance);
        }
    });
};

EmulatedInterface.prototype.geohash = function(member, zSetName, callBack) {

    var point;
    var hash;

    this.client.zscore(zSetName, member, function(err, result) {
        if (err) {
            callBack(err, null);
        } else {
            if (result !== null) {
                point = geohashDecode(result);
                hash = geohash.encode(point.latitude, point.longitude);
            } else {
                hash = null;
            }
            callBack(null, hash);
        }
    });
};

EmulatedInterface.prototype.geohashes = function(members, zSetName, callBack) {

    var locationSet = {};
    var point;
    var i;
    var multi = this.client.multi();

    for (i = 0; i < members.length; i++) {
        multi.zscore(zSetName, members[i]);
    }

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            for (i = 0; i < replies.length; i++) {
                if (replies[i] !== null) {
                    point = geohashDecode(replies[i]);
                    locationSet[members[i]] = geohash.encode(point.latitude, point.longitude);
                } else {
                    locationSet[members[i]] = null;
                }
            }

            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos_multi = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.georadius = function(point, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearby(point, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.georadiusbymember = function(member, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearbymember(member, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.nearby = function(point, distance, options, zSetName, callBack) {

    var ranges;
    var withValues = (options.precise || options.withDistances || options.withCoordinates || options.withHashes) ? true : false;
    var count = options.count;
    var units = options.units || 'm';

    distance = convertUnitsToMeters(units, distance);

    ranges = range(point.latitude, point.longitude, distance);

    query(this.client, zSetName, ranges, withValues, function(err, locations) {

        if (!err) {
            if (withValues === true) {
                locations = processLocations(locations, point, distance, options, callBack);
            } else {

                if (typeof count === 'number') {
                    locations = locations.slice(count);
                }

                Object.defineProperty(locations, 'locationSet', {
                    get: function() {
                        buildObjectFromStringArray(this);
                    }
                });
            }

            callBack(null, locations);
        } else {
            callBack(err, null);
        }
    });
};

EmulatedInterface.prototype.nearbymember = function(locationName, distance, options, zSetName, callBack) {

    var self = this;

    this.geopos([locationName], zSetName, function(err, location) {
        if (!err) {
            self.nearby(location, distance, options, zSetName, callBack);
        } else {
            callBack(err, null);
        }
    });
};

// Native Ordered Set Commands

EmulatedInterface.prototype.del = function(zSetName, callBack) {
    this.client.del(zSetName, callBack);
};

EmulatedInterface.prototype.zrem = function(locations, zSetName, callBack) {
    this.client.zrem(zSetName, locations, callBack);
};

function processLocations(locationSetOriginal, point, queryDistance, options, callBack) {

    var locations = [];

    var units = options.units || 'm';
    var withDistances = options.withDistances;
    var withCoordinates = options.withCoordinates;
    var withHashes = options.withHashes;
    var order = options.order;
    var count = options.count;
    var precise = options.precise;

    var locationName;
    var location;
    var distance;

    if (order) {

        locations = orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units);

    } else {

        for (locationName in locationSetOriginal) {

            location = locationSetOriginal[locationName];
            distance = geolib.getDistance(point, location, 1);

            if (precise !== true || distance <= queryDistance) {

                location.key = locationName;
                location.distance = convertUnitsFromMeters(units, distance);

                if (withHashes === true) {
                    location.hash = geohash.encode(location.latitude, location.longitude);
                }

                locations.push(location);
            }
        }
    }

    if (typeof count === 'number') {
        locations = locations.slice(count);
    }

    Object.defineProperty(locations, 'locationSet', {
        get: function() {
            buildObjectFromKeyedArray(this);
        }
    });

    return locations;
}

function orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units) {

    var orderedLocations = geolib.orderByDistance(point, locationSetOriginal);
    var location;
    var i;

    if (precise) {
        for (i = orderedLocations.length - 1; i > -1; i--) {
            location = orderedLocations[i];
            if (location.distance > queryDistance) {
                orderedLocations = orderedLocations.slice(i);
                break;
            }
        }
    }

    if (withHashes) {
        for (i = 0; i < orderedLocations.length; i++) {
            location = orderedLocations[i];
            location.hash = geohash.encode(location.latitude, location.longitude);
        }
    }

    if (order === 'DESC') {
        orderedLocations = orderedLocations.reverse();
    }

    return orderedLocations;
}

function geohashDecode(hash) {

    var decoded;
    var latlon;

    if (hash !== null) {

        latlon = geohash.decode_int(hash, 52);

        decoded = {
            latitude: latlon.latitude,
            longitude: latlon.longitude
        };

    } else {

        decoded = {
            latitude: null,
            longitude: null
        };
    }

    return decoded;
}

module.exports = EmulatedInterface;
<commit_msg>Better location set building logic and locations work.<commit_after>var geohash = require('ngeohash');
var geolib = require('geolib');

var helper = require('./util/helper');

var query = require('./query');
var range = require('./range');

var convertUnitsFromMeters = helper.convertUnitsFromMeters;
var convertUnitsToMeters = helper.convertUnitsToMeters;
var buildObjectFromKeyedArray = helper.buildObjectFromKeyedArray;
var buildObjectFromStringArray = helper.buildObjectFromStringArray;

// emulated interface constructor

function EmulatedInterface(client) {
    this.client = client;
    console.log('GeoRedis using EMULATED interface...');
}

// Native Geo Commands

EmulatedInterface.prototype.geoadd = function(locationName, point, zSetName, callBack) {
    this.client.zadd(zSetName, geohash.encode_int(point.latitude, point.longitude, 52), locationName, callBack);
};

EmulatedInterface.prototype.geoadd_multi = function(locationSet, zSetName, callBack) {
    var argArray = [zSetName];
    var locationName;
    var location;

    for (locationName in locationSet) {
        location = locationSet[locationName];
        argArray.push(geohash.encode_int(location.latitude, location.longitude, 52), locationName);
    }

    this.client.zadd(argArray, callBack);
};

EmulatedInterface.prototype.geodist = function(locationA, locationB, units, zSetName, callBack) {

    var multi = this.client.multi();
    var pointA;
    var pointB;
    var distance;

    multi.zscore(zSetName, locationA);
    multi.zscore(zSetName, locationB);

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            pointA = geohashDecode(replies[0]);
            pointB = geohashDecode(replies[1]);

            distance = geolib.getDistance(pointA, pointB, 1);
            distance = convertUnitsFromMeters(units, distance);

            callBack(null, distance);
        }
    });
};

EmulatedInterface.prototype.geohash = function(member, zSetName, callBack) {

    var point;
    var hash;

    this.client.zscore(zSetName, member, function(err, result) {
        if (err) {
            callBack(err, null);
        } else {
            if (result !== null) {
                point = geohashDecode(result);
                hash = geohash.encode(point.latitude, point.longitude);
            } else {
                hash = null;
            }
            callBack(null, hash);
        }
    });
};

EmulatedInterface.prototype.geohashes = function(members, zSetName, callBack) {

    var locationSet = {};
    var point;
    var i;
    var multi = this.client.multi();

    for (i = 0; i < members.length; i++) {
        multi.zscore(zSetName, members[i]);
    }

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            for (i = 0; i < replies.length; i++) {
                if (replies[i] !== null) {
                    point = geohashDecode(replies[i]);
                    locationSet[members[i]] = geohash.encode(point.latitude, point.longitude);
                } else {
                    locationSet[members[i]] = null;
                }
            }

            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos = function(member, zSetName, callBack) {

    var location;

    this.client.zscore(zSetName, member, function(err, result) {
        if (err) {
            callBack(err, null);
        } else {
            if (result !== null) {
                location = geohashDecode(result);
            } else {
                location = null;
            }
            callBack(null, location);
        }
    });
};

EmulatedInterface.prototype.geopos_multi = function(members, zSetName, callBack) {

    var locationSet = {};
    var i;
    var multi = this.client.multi();

    for (i = 0; i < members.length; i++) {
        multi.zscore(zSetName, members[i]);
    }

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            for (i = 0; i < replies.length; i++) {
                if (replies[i] !== null) {
                    locationSet[members[i]] = geohashDecode(replies[i]);
                } else {
                    locationSet[members[i]] = null;
                }
            }

            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.georadius = function(point, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearby(point, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.georadiusbymember = function(member, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearbymember(member, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.nearby = function(point, distance, options, zSetName, callBack) {

    var ranges;
    var count = options.count;
    var units = options.units || 'm';
    var precise = options.precise;
    var withValues = (precise || options.withDistances || options.withCoordinates || options.withHashes) ? true : false;

    distance = convertUnitsToMeters(units, distance);

    ranges = range(point.latitude, point.longitude, distance, precise);

    query(this.client, zSetName, ranges, withValues, function(err, locations) {

        if (!err) {
            if (withValues === true) {
                locations = processLocations(locations, point, distance, options, callBack);
            } else {

                if (typeof count === 'number') {
                    locations = locations.slice(count);
                }

                Object.defineProperty(locations, 'locationSet', {
                    get: function() {
                        return buildObjectFromStringArray(this);
                    }
                });
            }

            callBack(null, locations);
        } else {
            callBack(err, null);
        }
    });
};

EmulatedInterface.prototype.nearbymember = function(locationName, distance, options, zSetName, callBack) {

    var self = this;

    this.geopos([locationName], zSetName, function(err, location) {
        if (!err) {
            self.nearby(location, distance, options, zSetName, callBack);
        } else {
            callBack(err, null);
        }
    });
};

// Native Ordered Set Commands

EmulatedInterface.prototype.del = function(zSetName, callBack) {
    this.client.del(zSetName, callBack);
};

EmulatedInterface.prototype.zrem = function(locations, zSetName, callBack) {
    var argsArray = [zSetName].concat(locations);
    this.client.zrem(argsArray, callBack);
};

function processLocations(locationSetOriginal, point, queryDistance, options, callBack) {

    var locations = [];

    var units = options.units || 'm';
    var withDistances = options.withDistances;
    var withCoordinates = options.withCoordinates;
    var withHashes = options.withHashes;
    var order = options.order;
    var count = options.count;
    var precise = options.precise;
    var calculateDistances;

    var locationName;
    var location;
    var distance;

    if (order) {

        locations = orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units);

    } else {

        for (locationName in locationSetOriginal) {

            location = locationSetOriginal[locationName];

            if (precise === true) {

                distance = geolib.getDistance(point, location, 1);
                if (distance > queryDistance) {
                    continue;
                }
                location.distance = distance;

            } else if (withDistances === true) {

                distance = geolib.getDistance(point, location, 1);
                location.distance = convertUnitsFromMeters(units, distance);
            }
            if (withHashes === true) {
                location.hash = geohash.encode(location.latitude, location.longitude);
            }

            location.key = locationName;
            locations.push(location);
        }

        if (typeof count === 'number') {
            locations = locations.slice(count);
        }

    }

    Object.defineProperty(locations, 'locationSet', {
        get: function() {
            return buildObjectFromKeyedArray(this);
        }
    });

    return locations;
}

function orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units) {

    var orderedLocations = geolib.orderByDistance(point, locationSetOriginal);
    var location;
    var i;

    if (precise) {
        for (i = orderedLocations.length - 1; i > -1; i--) {
            location = orderedLocations[i];
            if (location.distance > queryDistance) {
                orderedLocations = orderedLocations.slice(i);
                break;
            }
        }
    }

    if (withHashes) {
        for (i = 0; i < orderedLocations.length; i++) {
            location = orderedLocations[i];
            location.hash = geohash.encode(location.latitude, location.longitude);
        }
    }

    if (order === 'DESC') {
        orderedLocations = orderedLocations.reverse();
    }

    return orderedLocations;
}

function geohashDecode(hash) {

    var decoded;
    var latlon;

    if (hash !== null) {

        latlon = geohash.decode_int(hash, 52);

        decoded = {
            latitude: latlon.latitude,
            longitude: latlon.longitude
        };

    } else {

        decoded = {
            latitude: null,
            longitude: null
        };
    }

    return decoded;
}

module.exports = EmulatedInterface;
<|endoftext|>