From 19f4b3b0bc888a14034b9a61f2008aedfae345c1 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@keylocation.sg>
Date: Sun, 25 Jun 2017 07:36:13 +0200
Subject: [PATCH] Refactor repository worker (#344)

* Move to subdir

* Downgrade eslint to 3

* Refactor api and config usage

* Refactor mergeRenovateJson

* Test mergeRenovateJson

* getOnboardingStatus tests

* Refactor repository structure

* Refactor config.logger

* Revert "Refactor config.logger"

This reverts commit 6d7f81af6ee284d01aab811dab7eb05c2274edf3.

* Refactor repository logging

* Refactor try/catch

* Refactor platform and onboarding

* Refactor setNpmrc

* Fix github logger

* npm api use config.logger

* Refactor repo worker logger

* Refactor repo worker

* Refactor branched upgrades

* Repository refactoring

* Move some debug logging to trace

* Deprecate fileName

* Refactor upgrades

* Refactor repository logs

* More repository log refactoring

* Refactor repository location

* Revert "Refactor repository location"

This reverts commit faecbf29516737a2752de54103c0228b9112a51c.

* Fix tests

* mergeRenovateJson

* Recombine repository worker

* Add initApis tests

* add detectPackageFiles tests

* Add determineRepoUpgrades tests

* start groupUpgradesByBranch tests

* add test

* add test

* Finish groupUpgradesByBranch coverage

* Test updateBranchesSequentially

* Finish repo coverage

* Finish branch worker coverage

* Finish workers coverage

* Fix isPin

* Complete workers coverage

* Finish helpers coverage

* Add gitlab api tests

* getBranchStatus tests

* test createPr

* start getPr testing

* getPr

* update and merge PR tests

* getFile

* getFileContent tests

* getFileJson tests

* createFile

* updateFile

* createBranch

* commitFilesToBranch

* update yarn

* Update yarn
---
 docs/configuration.md                         |   2 +-
 lib/api/github.js                             |   1 +
 lib/api/npm.js                                |   5 +-
 lib/config/index.js                           |   6 +-
 lib/helpers/platform.js                       |  16 +
 lib/helpers/versions.js                       |   6 +
 lib/workers/branch.js                         |  26 +-
 lib/workers/index.js                          |   7 +-
 lib/workers/package-file.js                   |  12 +-
 lib/workers/pr.js                             |   2 +-
 lib/workers/repository.js                     | 311 ++++++++--------
 test/_fixtures/config/file.js                 |   2 +-
 test/api/npm.spec.js                          |  11 +-
 .../__snapshots__/package-json.spec.js.snap   |   3 +
 .../__snapshots__/versions.spec.js.snap       |  31 ++
 test/helpers/changelog.spec.js                |   8 +-
 test/helpers/package-json.spec.js             | 110 +-----
 test/helpers/platform.spec.js                 |  95 +++++
 .../workers/__snapshots__/branch.spec.js.snap |  37 ++
 test/workers/__snapshots__/index.spec.js.snap |  26 ++
 .../repository-functions.spec.js.snap         |  95 +++++
 test/workers/branch.spec.js                   |  64 +++-
 test/workers/index.spec.js                    |  38 +-
 test/workers/package-file.spec.js             |   8 +-
 test/workers/pr.spec.js                       |   8 +-
 test/workers/repository-functions.spec.js     | 343 ++++++++++++++++++
 test/workers/repository.spec.js               |  43 ++-
 yarn.lock                                     |  24 +-
 28 files changed, 995 insertions(+), 345 deletions(-)
 create mode 100644 lib/helpers/platform.js
 create mode 100644 test/helpers/__snapshots__/package-json.spec.js.snap
 create mode 100644 test/helpers/platform.spec.js
 create mode 100644 test/workers/__snapshots__/index.spec.js.snap
 create mode 100644 test/workers/__snapshots__/repository-functions.spec.js.snap
 create mode 100644 test/workers/repository-functions.spec.js

diff --git a/docs/configuration.md b/docs/configuration.md
index 2eee94e6c7..0bb9905165 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -32,7 +32,7 @@ module.exports = {
       packageFiles: [
         'package.json',
         {
-          fileName: 'frontend/package.json',
+          packageFile: 'frontend/package.json',
           labels: ['upgrade', 'frontend']
         },
       ],
diff --git a/lib/api/github.js b/lib/api/github.js
index 29118debb8..7fba3274cf 100644
--- a/lib/api/github.js
+++ b/lib/api/github.js
@@ -124,6 +124,7 @@ async function getRepos(token, endpoint) {
 
 // Initialize GitHub by getting base branch and SHA
 async function initRepo(repoName, token, endpoint, repoLogger) {
+  logger = repoLogger || logger;
   logger.debug(`initRepo(${JSON.stringify(repoName)})`);
   if (repoLogger) {
     logger = repoLogger;
diff --git a/lib/api/npm.js b/lib/api/npm.js
index e4d476a435..409108b5c6 100644
--- a/lib/api/npm.js
+++ b/lib/api/npm.js
@@ -4,7 +4,6 @@ const got = require('got');
 const url = require('url');
 const registryUrl = require('registry-url');
 const registryAuthToken = require('registry-auth-token');
-const logger = require('../helpers/logger');
 
 module.exports = {
   setNpmrc,
@@ -23,7 +22,7 @@ async function setNpmrc(input) {
   npmrc = input;
 }
 
-async function getDependency(name) {
+async function getDependency(name, logger) {
   logger.debug(`getDependency(${name})`);
   const scope = name.split('/')[0];
   const regUrl = registryUrl(scope, { npmrc });
@@ -66,7 +65,7 @@ async function getDependency(name) {
       dep.versions[version] = {};
     });
     npmCache[cacheKey] = dep;
-    logger.debug(JSON.stringify(dep));
+    logger.trace({ dependency: dep }, JSON.stringify(dep));
     return dep;
   } catch (err) {
     logger.warn(`Dependency not found: ${name}`);
diff --git a/lib/config/index.js b/lib/config/index.js
index 341091161d..5dff1c6cc0 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -46,13 +46,13 @@ async function parseConfigs(env, argv) {
     });
   }
 
-  logger.debug({ config: defaultConfig }, 'Default config');
+  logger.trace({ config: defaultConfig }, 'Default config');
   logger.debug({ config: fileConfig }, 'File config');
   logger.debug({ config: cliConfig }, 'CLI config');
   logger.debug({ config: envConfig }, 'Env config');
 
   // Get global config
-  logger.debug({ config }, 'Raw config');
+  logger.trace({ config }, 'Raw config');
 
   // Check platforms and tokens
   if (config.platform === 'github') {
@@ -107,7 +107,7 @@ async function parseConfigs(env, argv) {
   }
 
   // Print config
-  logger.debug({ config }, 'Global config');
+  logger.trace({ config }, 'Global config');
   // Remove log file entries
   delete config.logFile;
   delete config.logFileLevel;
diff --git a/lib/helpers/platform.js b/lib/helpers/platform.js
new file mode 100644
index 0000000000..8255006a48
--- /dev/null
+++ b/lib/helpers/platform.js
@@ -0,0 +1,16 @@
+const githubApi = require('../../lib/api/github');
+const gitlabApi = require('../../lib/api/gitlab');
+
+module.exports = {
+  // TODO: Centralise platform-specific functions here (e.g. wording)
+  getApi,
+};
+
+function getApi(platform) {
+  if (platform === 'github') {
+    return githubApi;
+  } else if (platform === 'gitlab') {
+    return gitlabApi;
+  }
+  throw new Error(`Unknown platform: ${platform}`);
+}
diff --git a/lib/helpers/versions.js b/lib/helpers/versions.js
index 357ed95123..8c684fe17f 100644
--- a/lib/helpers/versions.js
+++ b/lib/helpers/versions.js
@@ -31,6 +31,7 @@ function determineUpgrades(dep, currentVersion, config) {
     const maxSatisfying = semver.maxSatisfying(versionList, currentVersion);
     allUpgrades.pin = {
       upgradeType: 'pin',
+      isPin: true,
       newVersion: maxSatisfying,
       newVersionMajor: semver.major(maxSatisfying),
     };
@@ -90,6 +91,11 @@ function determineUpgrades(dep, currentVersion, config) {
           changeLogToVersion,
           automergeEnabled,
         };
+        if (upgradeType === 'major') {
+          allUpgrades[upgradeKey].isMajor = true;
+        } else if (upgradeType === 'minor') {
+          allUpgrades[upgradeKey].isMinor = true;
+        }
       }
     });
   if (allUpgrades.pin && Object.keys(allUpgrades).length > 1) {
diff --git a/lib/workers/branch.js b/lib/workers/branch.js
index 01df868f2b..8e75b18301 100644
--- a/lib/workers/branch.js
+++ b/lib/workers/branch.js
@@ -9,6 +9,7 @@ module.exports = {
   getParentBranch,
   ensureBranch,
   updateBranch,
+  removeStandaloneBranches,
 };
 
 async function getParentBranch(branchName, config) {
@@ -60,7 +61,7 @@ async function getParentBranch(branchName, config) {
 
 // Ensure branch exists with appropriate content
 async function ensureBranch(upgrades) {
-  logger.debug({ config: upgrades }, 'ensureBranch');
+  logger.trace({ config: upgrades }, 'ensureBranch');
   // Use the first upgrade for all the templates
   const branchName = handlebars.compile(upgrades[0].branchName)(upgrades[0]);
   // parentBranch is the branch we will base off
@@ -197,13 +198,14 @@ async function ensureBranch(upgrades) {
   return true;
 }
 
-async function updateBranch(upgrades, parentLogger) {
+async function updateBranch(upgrades) {
+  await removeStandaloneBranches(upgrades);
   const upgrade0 = upgrades[0];
   // Use templates to generate strings
   const branchName = handlebars.compile(upgrade0.branchName)(upgrade0);
   const prTitle = handlebars.compile(upgrade0.prTitle)(upgrade0);
 
-  logger = parentLogger.child({
+  logger = upgrade0.logger.child({
     repository: upgrade0.repository,
     branch: branchName,
   });
@@ -241,3 +243,21 @@ async function updateBranch(upgrades, parentLogger) {
     // Don't throw here - we don't want to stop the other renovations
   }
 }
+
+async function removeStandaloneBranches(upgrades) {
+  if (upgrades.length > 1) {
+    for (const upgrade of upgrades) {
+      const standaloneBranchName = handlebars.compile(upgrade.branchName)(
+        upgrade
+      );
+      upgrade.logger.debug(`Need to delete branch ${standaloneBranchName}`);
+      try {
+        await upgrade.api.deleteBranch(standaloneBranchName);
+      } catch (err) {
+        upgrade.logger.debug(`Couldn't delete branch ${standaloneBranchName}`);
+      }
+      // Rename to group branchName
+      upgrade.branchName = upgrade.groupBranchName;
+    }
+  }
+}
diff --git a/lib/workers/index.js b/lib/workers/index.js
index 6c3a3e8b27..b1d7fe3929 100644
--- a/lib/workers/index.js
+++ b/lib/workers/index.js
@@ -7,15 +7,16 @@ module.exports = {
 };
 
 async function start() {
-  // Parse config
+  logger.info('Renovate starting');
   try {
-    logger.info('Renovate starting');
     const config = await configParser.parseConfigs(process.env, process.argv);
     // Iterate through repositories sequentially
     for (let index = 0; index < config.repositories.length; index += 1) {
       const repoConfig = configParser.getRepoConfig(config, index);
-      repoConfig.logger = logger;
+      repoConfig.logger = logger.child({ repository: repoConfig.repository });
+      repoConfig.logger.info('Renovating repository');
       await repositoryWorker.processRepo(repoConfig);
+      repoConfig.logger.info('Finished repository');
     }
     logger.info('Renovate finished');
   } catch (err) {
diff --git a/lib/workers/package-file.js b/lib/workers/package-file.js
index 712ecb028d..ae1bd98b74 100644
--- a/lib/workers/package-file.js
+++ b/lib/workers/package-file.js
@@ -36,7 +36,9 @@ async function processPackageFile(config) {
       { config: packageContent.renovate },
       'package.json>renovate config'
     );
-    Object.assign(config, packageContent.renovate, { repoConfigured: true });
+    Object.assign(config, packageContent.renovate, {
+      renovateJsonPresent: true,
+    });
   }
   // Now check if config is disabled
   if (config.enabled === false) {
@@ -118,7 +120,7 @@ function assignDepConfigs(inputConfig, deps) {
     delete returnDep.config.githubAppKey;
     delete returnDep.config.packageFiles;
     delete returnDep.config.logLevel;
-    delete returnDep.config.repoConfigured;
+    delete returnDep.config.renovateJsonPresent;
     delete returnDep.config.ignoreDeps;
     delete returnDep.config.packages;
     delete returnDep.config.maintainYarnLock;
@@ -134,7 +136,7 @@ async function findUpgrades(dependencies) {
   const allUpgrades = [];
   // findDepUpgrades can add more than one upgrade to allUpgrades
   async function findDepUpgrades(dep) {
-    const npmDependency = await npmApi.getDependency(dep.depName);
+    const npmDependency = await npmApi.getDependency(dep.depName, logger);
     if (!npmDependency) {
       // If dependency lookup fails then ignore it and keep going
       return;
@@ -154,7 +156,9 @@ async function findUpgrades(dependencies) {
         )}`
       );
       upgrades.forEach(upgrade => {
-        allUpgrades.push(Object.assign({}, dep, upgrade));
+        const upgradeObj = Object.assign({}, dep, dep.config, upgrade);
+        delete upgradeObj.config;
+        allUpgrades.push(upgradeObj);
       });
     } else {
       logger.debug(`${dep.depName}: No upgrades required`);
diff --git a/lib/workers/pr.js b/lib/workers/pr.js
index 6e91bf32e1..d03d72f443 100644
--- a/lib/workers/pr.js
+++ b/lib/workers/pr.js
@@ -12,7 +12,7 @@ module.exports = {
 
 // Ensures that PR exists with matching title/body
 async function ensurePr(upgrades, logger) {
-  logger.debug({ config: upgrades }, 'ensurePr');
+  logger.trace({ config: upgrades }, 'ensurePr');
   // If there is a group, it will use the config of the first upgrade in the array
   const config = Object.assign({}, upgrades[0]);
   config.upgrades = [];
diff --git a/lib/workers/repository.js b/lib/workers/repository.js
index b4e80398df..ec96f9581b 100644
--- a/lib/workers/repository.js
+++ b/lib/workers/repository.js
@@ -1,132 +1,77 @@
-// Global requires
+// Third party requires
 const handlebars = require('handlebars');
 const ini = require('ini');
-let logger = require('../helpers/logger');
 const stringify = require('json-stringify-pretty-compact');
-// API
-const githubApi = require('../api/github');
-const gitlabApi = require('../api/gitlab');
-const npmApi = require('../api/npm');
 // Config
 const defaultsParser = require('../config/defaults');
+// API
+const githubApi = require('../../lib/api/github');
+const gitlabApi = require('../../lib/api/gitlab');
+const npmApi = require('../api/npm');
 // Workers
-const packageFileWorker = require('./package-file');
 const branchWorker = require('./branch');
+const packageFileWorker = require('./package-file');
 
 module.exports = {
-  processRepo,
-  processUpgrades,
-  removeStandaloneBranches,
-  mergeRenovateJson,
-  checkIfOnboarded,
   setNpmrc,
+  initApis,
+  mergeRenovateJson,
+  onboardRepository,
+  getOnboardingStatus,
   detectPackageFiles,
-  getAllRepoUpgrades,
+  determineRepoUpgrades,
+  groupUpgradesByBranch,
+  updateBranchesSequentially,
+  processRepo,
 };
 
-// This will be github or others
-let api;
-
-// Queue package files in sequence within a repo
-async function processRepo(config) {
-  logger = config.logger.child({ repository: config.repository });
-  config.logger = logger; // eslint-disable-line no-param-reassign
-  logger.info('Renovating repository');
-  logger.debug({ config }, 'processRepo');
-  if (config.platform === 'github') {
-    api = githubApi;
-  } else if (config.platform === 'gitlab') {
-    api = gitlabApi;
-  } else {
-    // TODO: throw this?
-    logger.error(
-      `Unknown platform ${config.platform} for repository ${config.repository}`
-    );
-    return;
-  }
-  try {
-    // Initialize repo
-    await api.initRepo(
-      config.repository,
-      config.token,
-      config.endpoint,
-      logger
-    );
-    // Override settings with renovate.json if present
-    await module.exports.mergeRenovateJson(config);
-    // Check that the repository is onboarded
-    const isOnboarded = await module.exports.checkIfOnboarded(config);
-    if (isOnboarded === false) {
-      return;
-    }
-    // Check for presence of .npmrc in repository
-    await module.exports.setNpmrc(config);
-    // Detect package files if none already configured
-    await module.exports.detectPackageFiles(config);
-    const upgrades = await module.exports.getAllRepoUpgrades(config);
-    await module.exports.processUpgrades(upgrades);
-  } catch (error) {
-    throw error;
-  }
-  logger.info('Finished repository');
-}
-
-// Check for config in `renovate.json`
-async function mergeRenovateJson(config) {
-  const renovateJson = await api.getFileJson('renovate.json');
-  if (renovateJson) {
-    logger.debug({ config: renovateJson }, 'renovate.json config');
-    Object.assign(config, renovateJson, { repoConfigured: true });
-  } else {
-    logger.debug('No renovate.json found');
-  }
-}
-
 // Check for .npmrc in repository and pass it to npm api if found
-async function setNpmrc() {
+async function setNpmrc(config) {
   try {
     let npmrc = null;
-    const npmrcContent = await api.getFileContent('.npmrc');
+    const npmrcContent = await config.api.getFileContent('.npmrc');
     if (npmrcContent) {
-      logger.debug('Found .npmrc file in repository');
+      config.logger.debug('Found .npmrc file in repository');
       npmrc = ini.parse(npmrcContent);
     }
     npmApi.setNpmrc(npmrc);
   } catch (err) {
-    logger.error('Failed to set .npmrc');
+    config.logger.error('Failed to set .npmrc');
   }
 }
 
-async function checkIfOnboarded(config) {
-  logger.debug('Checking if repo is configured');
-  // Check if repository is configured
-  if (config.repoConfigured || config.onboarding === false) {
-    logger.debug('Repo is configured or onboarding disabled');
-    return true;
-  }
-  const pr = await api.findPr('renovate/configure', 'Configure Renovate');
-  if (pr) {
-    if (pr.isClosed) {
-      logger.debug('Closed Configure Renovate PR found - continuing');
-      return true;
+async function initApis(inputConfig) {
+  function getPlatformApi(platform) {
+    if (platform === 'github') {
+      return githubApi;
+    } else if (platform === 'gitlab') {
+      return gitlabApi;
     }
-    // PR exists but hasn't been closed yet
-    logger.error(`Close PR #${pr.displayNumber} before continuing`);
-    return false;
+    throw new Error(`Unknown platform: ${platform}`);
   }
-  await onboardRepository(config);
-  return false;
+
+  const config = Object.assign({}, inputConfig);
+  config.api = getPlatformApi(config.platform);
+  await config.api.initRepo(
+    config.repository,
+    config.token,
+    config.endpoint,
+    config.logger
+  );
+  // Check for presence of .npmrc in repository
+  await module.exports.setNpmrc(config);
+  return config;
 }
 
-// Ensure config contains packageFiles
-async function detectPackageFiles(config) {
-  if (config.packageFiles.length === 0) {
-    // autodiscover filenames if none manually configured
-    const fileNames = await api.findFilePaths('package.json');
-    // Map to config structure
-    const packageFiles = fileNames.map(fileName => ({ fileName }));
-    Object.assign(config, { packageFiles });
+// Check for config in `renovate.json`
+async function mergeRenovateJson(config) {
+  const renovateJson = await config.api.getFileJson('renovate.json');
+  if (!renovateJson) {
+    config.logger.debug('No renovate.json found');
+    return config;
   }
+  config.logger.debug({ config: renovateJson }, 'renovate.json config');
+  return Object.assign({}, config, renovateJson, { renovateJsonPresent: true });
 }
 
 async function onboardRepository(config) {
@@ -155,7 +100,7 @@ If the default settings are all suitable for you, simply close this Pull Request
     prBody = prBody.replace(/Pull Request/g, 'Merge Request');
   }
   const defaultConfigString = `${stringify(defaultConfig)}\n`;
-  await api.commitFilesToBranch(
+  await config.api.commitFilesToBranch(
     'renovate/configure',
     [
       {
@@ -165,97 +110,139 @@ If the default settings are all suitable for you, simply close this Pull Request
     ],
     'Add renovate.json'
   );
-  const pr = await api.createPr(
+  const pr = await config.api.createPr(
     'renovate/configure',
     'Configure Renovate',
     prBody
   );
-  logger.info(`Created ${pr.displayNumber} for configuration`);
+  config.logger.debug(`Created ${pr.displayNumber} for configuration`);
 }
 
-async function getAllRepoUpgrades(config) {
-  logger.info('getAllRepoUpgrades');
+async function getOnboardingStatus(config) {
+  config.logger.debug('Checking if repo is configured');
+  // Check if repository is configured
+  if (config.onboarding === false) {
+    config.logger.debug('Repo onboarding is disabled');
+    return true;
+  }
+  if (config.renovateJsonPresent) {
+    config.logger.debug('Repo onboarded');
+    return true;
+  }
+  const pr = await config.api.findPr(
+    'renovate/configure',
+    'Configure Renovate'
+  );
+  if (pr) {
+    if (pr.isClosed) {
+      config.logger.debug('Found closed Configure Renovate PR');
+      return true;
+    }
+    // PR exists but hasn't been closed yet
+    config.logger.debug(
+      `PR #${pr.displayNumber} needs to be closed to enable renovate to continue`
+    );
+    return false;
+  }
+  await module.exports.onboardRepository(config);
+  return false;
+}
+
+async function detectPackageFiles(config) {
+  config.logger.trace({ config }, 'detectPackageFiles');
+  const packageFiles = await config.api.findFilePaths('package.json');
+  config.logger.debug(`Found ${packageFiles.length} package file(s)`);
+  return Object.assign({}, config, { packageFiles });
+}
+
+async function determineRepoUpgrades(config) {
+  config.logger.trace({ config }, 'determineRepoUpgrades');
+  if (config.packageFiles.length === 0) {
+    config.logger.warn('No package files found');
+  }
   let upgrades = [];
   for (let packageFile of config.packageFiles) {
     if (typeof packageFile === 'string') {
-      packageFile = { fileName: packageFile };
+      packageFile = { packageFile };
+    } else if (packageFile.fileName) {
+      // Retained deprecated 'fileName' for backwards compatibility
+      // TODO: Remove in renovate 9
+      packageFile.packageFile = packageFile.fileName;
+      delete packageFile.fileName;
     }
-    const cascadedConfig = Object.assign({}, config, packageFile);
-    // Remove unnecessary fields
-    cascadedConfig.packageFile = cascadedConfig.fileName;
-    delete cascadedConfig.fileName;
+    const packageFileConfig = Object.assign({}, config, packageFile);
+    delete packageFileConfig.packageFiles;
     upgrades = upgrades.concat(
-      await packageFileWorker.processPackageFile(cascadedConfig)
+      await packageFileWorker.processPackageFile(packageFileConfig)
     );
   }
   return upgrades;
 }
 
-async function processUpgrades(upgrades) {
-  if (upgrades.length) {
-    const upgradeCount = upgrades.length === 1
-      ? '1 dependency upgrade'
-      : `${upgrades.length} dependency upgrades`;
-    logger.info(`Processing ${upgradeCount}`);
-  } else {
-    logger.info('No upgrades to process');
-  }
-  logger.debug({ config: upgrades }, 'All upgrades');
+async function groupUpgradesByBranch(upgrades, logger) {
+  logger.trace({ config: upgrades }, 'groupUpgradesByBranch');
+  logger.info(`Processing ${upgrades.length} dependency upgrade(s)`);
   const branchUpgrades = {};
-  for (const upgrade of upgrades) {
-    const flattened = Object.assign({}, upgrade.config, upgrade);
-    delete flattened.config;
-    if (flattened.upgradeType === 'pin') {
-      flattened.isPin = true;
-    } else if (flattened.upgradeType === 'major') {
-      flattened.isMajor = true;
-    } else if (flattened.upgradeType === 'minor') {
-      flattened.isMinor = true;
-    }
+  for (const upg of upgrades) {
+    const upgrade = Object.assign({}, upg);
     // Check whether to use a group name
     let branchName;
-    if (flattened.groupName) {
+    if (upgrade.groupName) {
+      upgrade.groupSlug =
+        upgrade.groupSlug ||
+        upgrade.groupName.toLowerCase().replace(/[^a-z0-9+]+/g, '-');
+      branchName = handlebars.compile(upgrade.groupBranchName)(upgrade);
       logger.debug(
-        `Dependency ${flattened.depName} is part of group '${flattened.groupName}'`
+        { branchName },
+        `Dependency ${upgrade.depName} is part of group '${upgrade.groupName}'`
       );
-      flattened.groupSlug =
-        flattened.groupSlug ||
-        flattened.groupName.toLowerCase().replace(/[^a-z0-9+]+/g, '-');
-      branchName = handlebars.compile(flattened.groupBranchName)(flattened);
-      logger.debug(`branchName=${branchName}`);
       if (branchUpgrades[branchName]) {
-        // flattened.branchName = flattened.groupBranchName;
-        flattened.commitMessage = flattened.groupCommitMessage;
-        flattened.prTitle = flattened.groupPrTitle;
-        flattened.prBody = flattened.groupPrBody;
+        upgrade.commitMessage = upgrade.groupCommitMessage;
+        upgrade.prTitle = upgrade.groupPrTitle;
+        upgrade.prBody = upgrade.groupPrBody;
       }
     } else {
-      branchName = handlebars.compile(flattened.branchName)(flattened);
+      branchName = handlebars.compile(upgrade.branchName)(upgrade);
     }
     branchUpgrades[branchName] = branchUpgrades[branchName] || [];
-    branchUpgrades[branchName] = [flattened].concat(branchUpgrades[branchName]);
+    branchUpgrades[branchName] = [upgrade].concat(branchUpgrades[branchName]);
   }
-  logger.debug({ config: branchUpgrades }, 'Branched upgrades');
-  for (const branch of Object.keys(branchUpgrades)) {
-    await module.exports.removeStandaloneBranches(branchUpgrades[branch]);
-    await branchWorker.updateBranch(branchUpgrades[branch], logger);
+  logger.debug(`Returning ${Object.keys(branchUpgrades).length} branch(es)`);
+  return branchUpgrades;
+}
+
+async function updateBranchesSequentially(branchUpgrades, logger) {
+  logger.trace({ config: branchUpgrades }, 'updateBranchesSequentially');
+  logger.debug(`Updating ${Object.keys(branchUpgrades).length} branch(es)`);
+  for (const branchName of Object.keys(branchUpgrades)) {
+    await branchWorker.updateBranch(branchUpgrades[branchName]);
   }
 }
 
-async function removeStandaloneBranches(upgrades) {
-  if (upgrades.length > 1) {
-    for (const upgrade of upgrades) {
-      const standaloneBranchName = handlebars.compile(upgrade.branchName)(
-        upgrade
-      );
-      logger.debug(`Need to delete branch ${standaloneBranchName}`);
-      try {
-        await upgrade.api.deleteBranch(standaloneBranchName);
-      } catch (err) {
-        logger.debug(`Couldn't delete branch ${standaloneBranchName}`);
-      }
-      // Rename to group branchName
-      upgrade.branchName = upgrade.groupBranchName;
+async function processRepo(repoConfig) {
+  let config = Object.assign({}, repoConfig);
+  config.logger.trace({ config }, 'processRepo');
+  try {
+    config = await module.exports.initApis(config);
+    config = await module.exports.mergeRenovateJson(config);
+    const repoIsOnboarded = await module.exports.getOnboardingStatus(config);
+    if (!repoIsOnboarded) {
+      config.logger.info('"Configure Renovate" PR needs to be closed first');
+      return;
+    }
+    const hasConfiguredPackageFiles = config.packageFiles.length > 0;
+    if (!hasConfiguredPackageFiles) {
+      config = await module.exports.detectPackageFiles(config);
     }
+    const allUpgrades = await module.exports.determineRepoUpgrades(config);
+    const branchUpgrades = await module.exports.groupUpgradesByBranch(
+      allUpgrades,
+      config.logger
+    );
+    await updateBranchesSequentially(branchUpgrades, config.logger);
+  } catch (error) {
+    // Swallow this error so that other repositories can be processed
+    config.logger.error(`Failed to process repository: ${error.message}`);
+    config.logger.debug({ error });
   }
 }
diff --git a/test/_fixtures/config/file.js b/test/_fixtures/config/file.js
index 7ccccf04de..61e930fe6b 100644
--- a/test/_fixtures/config/file.js
+++ b/test/_fixtures/config/file.js
@@ -11,7 +11,7 @@ module.exports = {
       repository: 'singapore/renovate',
       packageFiles: [
         {
-          fileName: 'package.json',
+          packageFile: 'package.json',
           labels: ['a'],
         },
       ],
diff --git a/test/api/npm.spec.js b/test/api/npm.spec.js
index 0989b12b6d..9f1ed8804f 100644
--- a/test/api/npm.spec.js
+++ b/test/api/npm.spec.js
@@ -2,6 +2,7 @@ const npm = require('../../lib/api/npm');
 const got = require('got');
 const registryUrl = require('registry-url');
 const registryAuthToken = require('registry-auth-token');
+const logger = require('../_fixtures/logger');
 
 jest.mock('registry-url');
 jest.mock('registry-auth-token');
@@ -25,7 +26,7 @@ describe('api/npm', () => {
   it('should fetch package info from npm', async () => {
     registryUrl.mockImplementation(() => 'https://npm.mycustomregistry.com/');
     got.mockImplementation(() => Promise.resolve(npmResponse));
-    const res = await npm.getDependency('foobar');
+    const res = await npm.getDependency('foobar', logger);
     expect(res).toMatchSnapshot();
     const call = got.mock.calls[0];
     expect(call).toMatchSnapshot();
@@ -33,8 +34,8 @@ describe('api/npm', () => {
   it('should cache package info from npm', async () => {
     registryUrl.mockImplementation(() => 'https://npm.mycustomregistry.com/');
     got.mockImplementation(() => Promise.resolve(npmResponse));
-    const res1 = await npm.getDependency('foobar');
-    const res2 = await npm.getDependency('foobar');
+    const res1 = await npm.getDependency('foobar', logger);
+    const res2 = await npm.getDependency('foobar', logger);
     expect(res1).toEqual(res2);
     expect(got.mock.calls.length).toEqual(1);
   });
@@ -43,7 +44,7 @@ describe('api/npm', () => {
     got.mockImplementation(() => {
       throw new Error('not found');
     });
-    const res = await npm.getDependency('foobar');
+    const res = await npm.getDependency('foobar', logger);
     expect(res).toBeNull();
   });
   it('should send an authorization header if provided', async () => {
@@ -53,7 +54,7 @@ describe('api/npm', () => {
       token: '1234',
     }));
     got.mockImplementation(() => Promise.resolve(npmResponse));
-    const res = await npm.getDependency('foobar');
+    const res = await npm.getDependency('foobar', logger);
     expect(res).toMatchSnapshot();
     const call = got.mock.calls[0];
     expect(call).toMatchSnapshot();
diff --git a/test/helpers/__snapshots__/package-json.spec.js.snap b/test/helpers/__snapshots__/package-json.spec.js.snap
new file mode 100644
index 0000000000..856503e452
--- /dev/null
+++ b/test/helpers/__snapshots__/package-json.spec.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`helpers/platform getApi(platform) throws error 1`] = `[Error: Unknown platform: foo]`;
diff --git a/test/helpers/__snapshots__/versions.spec.js.snap b/test/helpers/__snapshots__/versions.spec.js.snap
index d9b94b59bc..82ec942873 100644
--- a/test/helpers/__snapshots__/versions.spec.js.snap
+++ b/test/helpers/__snapshots__/versions.spec.js.snap
@@ -6,6 +6,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -19,6 +20,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.0.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "minor",
@@ -32,6 +34,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.9.7",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -42,6 +45,7 @@ Array [
 exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) pins minor ranged versions 1`] = `
 Array [
   Object {
+    "isPin": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "pin",
@@ -55,6 +59,7 @@ Array [
     "automergeEnabled": true,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "newVersion": "0.9.7",
     "newVersionMajor": 0,
     "upgradeType": "minor",
@@ -63,6 +68,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -76,6 +82,7 @@ Array [
     "automergeEnabled": true,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -89,6 +96,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -102,6 +110,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.0.0-beta",
     "changeLogToVersion": "1.1.0-beta",
+    "isMinor": true,
     "newVersion": "1.1.0-beta",
     "newVersionMajor": 1,
     "upgradeType": "minor",
@@ -115,6 +124,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.0.34",
     "changeLogToVersion": "0.0.35",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "^0.0.35",
     "newVersionMajor": 0,
@@ -129,6 +139,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.4.1",
     "changeLogToVersion": "2.0.1",
+    "isMajor": true,
     "newVersion": "2.0.1",
     "newVersionMajor": 2,
     "upgradeType": "major",
@@ -139,6 +150,7 @@ Array [
 exports[`helpers/versions .determineUpgrades(dep, currentVersion, defaultConfig) supports future versions if already future 1`] = `
 Array [
   Object {
+    "isPin": true,
     "newVersion": "2.0.3",
     "newVersionMajor": 2,
     "upgradeType": "pin",
@@ -152,6 +164,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.4.1",
     "changeLogToVersion": "2.0.3",
+    "isMajor": true,
     "newVersion": "2.0.3",
     "newVersionMajor": 2,
     "upgradeType": "major",
@@ -165,6 +178,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "newVersion": "0.9.7",
     "newVersionMajor": 0,
     "upgradeType": "minor",
@@ -173,6 +187,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -186,6 +201,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "newVersion": "0.9.7",
     "newVersionMajor": 0,
     "upgradeType": "minor",
@@ -194,6 +210,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.4.4",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "major",
@@ -207,6 +224,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.9.7",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "isRange": true,
     "newVersion": "1.x",
     "newVersionMajor": 1,
@@ -221,6 +239,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.3.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "minor",
@@ -234,6 +253,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.3.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "1.4.x",
     "newVersionMajor": 1,
@@ -248,6 +268,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "<= 0.9.7",
     "newVersionMajor": 0,
@@ -257,6 +278,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "isRange": true,
     "newVersion": "<= 1.4.1",
     "newVersionMajor": 1,
@@ -271,6 +293,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.0.1",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "minor",
@@ -284,6 +307,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "^0.9.0",
     "newVersionMajor": 0,
@@ -293,6 +317,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "isRange": true,
     "newVersion": "^1.0.0",
     "newVersionMajor": 1,
@@ -307,6 +332,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "0.9.7",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "~0.9.0",
     "newVersionMajor": 0,
@@ -316,6 +342,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.7.2",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "isRange": true,
     "newVersion": "~1.4.0",
     "newVersionMajor": 1,
@@ -330,6 +357,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "0.9.7",
     "changeLogToVersion": "1.4.1",
+    "isMajor": true,
     "isRange": true,
     "newVersion": "1",
     "newVersionMajor": 1,
@@ -344,6 +372,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.3.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "1.4",
     "newVersionMajor": 1,
@@ -358,6 +387,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.3.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "newVersion": "1.4.1",
     "newVersionMajor": 1,
     "upgradeType": "minor",
@@ -371,6 +401,7 @@ Array [
     "automergeEnabled": false,
     "changeLogFromVersion": "1.3.0",
     "changeLogToVersion": "1.4.1",
+    "isMinor": true,
     "isRange": true,
     "newVersion": "~1.4.0",
     "newVersionMajor": 1,
diff --git a/test/helpers/changelog.spec.js b/test/helpers/changelog.spec.js
index 81f3a7d3e8..2c25e0dbba 100644
--- a/test/helpers/changelog.spec.js
+++ b/test/helpers/changelog.spec.js
@@ -1,12 +1,6 @@
 const changelog = require('changelog');
 const changelogHelper = require('../../lib/helpers/changelog');
-const bunyan = require('bunyan');
-
-const logger = bunyan.createLogger({
-  name: 'test',
-  stream: process.stdout,
-  level: 'fatal',
-});
+const logger = require('../_fixtures/logger');
 
 jest.mock('changelog');
 
diff --git a/test/helpers/package-json.spec.js b/test/helpers/package-json.spec.js
index a973c30091..60fd09ddc5 100644
--- a/test/helpers/package-json.spec.js
+++ b/test/helpers/package-json.spec.js
@@ -1,101 +1,21 @@
-const fs = require('fs');
-const path = require('path');
-const packageJson = require('../../lib/helpers/package-json');
-const bunyan = require('bunyan');
+const platformHelper = require('../../lib/helpers/platform');
 
-const logger = bunyan.createLogger({
-  name: 'test',
-  stream: process.stdout,
-  level: 'fatal',
-});
-
-const defaultTypes = [
-  'dependencies',
-  'devDependencies',
-  'optionalDependencies',
-];
-
-function readFixture(fixture) {
-  return fs.readFileSync(
-    path.resolve(__dirname, `../_fixtures/package-json/${fixture}`),
-    'utf8'
-  );
-}
-
-const input01Content = readFixture('inputs/01.json');
-const input02Content = readFixture('inputs/02.json');
-
-describe('helpers/package-json', () => {
-  describe('.extractDependencies(packageJson, sections)', () => {
-    it('returns an array of correct length', () => {
-      const extractedDependencies = packageJson.extractDependencies(
-        JSON.parse(input01Content),
-        defaultTypes
-      );
-      extractedDependencies.should.be.instanceof(Array);
-      extractedDependencies.should.have.length(10);
-    });
-    it('each element contains non-null depType, depName, currentVersion', () => {
-      const extractedDependencies = packageJson.extractDependencies(
-        JSON.parse(input01Content),
-        defaultTypes
-      );
-      extractedDependencies
-        .every(dep => dep.depType && dep.depName && dep.currentVersion)
-        .should.eql(true);
-    });
-    it('supports null devDependencies', () => {
-      const extractedDependencies = packageJson.extractDependencies(
-        JSON.parse(input02Content),
-        defaultTypes
-      );
-      extractedDependencies.should.be.instanceof(Array);
-      extractedDependencies.should.have.length(6);
-    });
-  });
-  describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => {
-    it('replaces a dependency value', () => {
-      const outputContent = readFixture('outputs/011.json');
-      const testContent = packageJson.setNewValue(
-        input01Content,
-        'dependencies',
-        'cheerio',
-        '0.22.1',
-        logger
-      );
-      testContent.should.equal(outputContent);
-    });
-    it('replaces only the first instance of a value', () => {
-      const outputContent = readFixture('outputs/012.json');
-      const testContent = packageJson.setNewValue(
-        input01Content,
-        'devDependencies',
-        'angular-touch',
-        '1.6.1',
-        logger
-      );
-      testContent.should.equal(outputContent);
+describe('helpers/platform', () => {
+  describe('getApi(platform)', () => {
+    it('returns github', () => {
+      platformHelper.getApi('github');
     });
-    it('replaces only the second instance of a value', () => {
-      const outputContent = readFixture('outputs/013.json');
-      const testContent = packageJson.setNewValue(
-        input01Content,
-        'devDependencies',
-        'angular-sanitize',
-        '1.6.1',
-        logger
-      );
-      testContent.should.equal(outputContent);
+    it('returns gitlab', () => {
+      platformHelper.getApi('gitlab');
     });
-    it('handles the case where the desired version is already supported', () => {
-      const testContent = packageJson.setNewValue(
-        input01Content,
-        'devDependencies',
-        'angular-touch',
-        '1.5.8',
-        logger
-      );
-      testContent.should.equal(input01Content);
+    it('throws error', () => {
+      let e;
+      try {
+        platformHelper.getApi('foo');
+      } catch (err) {
+        e = err;
+      }
+      expect(e).toMatchSnapshot();
     });
   });
 });
diff --git a/test/helpers/platform.spec.js b/test/helpers/platform.spec.js
new file mode 100644
index 0000000000..8b71735a81
--- /dev/null
+++ b/test/helpers/platform.spec.js
@@ -0,0 +1,95 @@
+const fs = require('fs');
+const path = require('path');
+const packageJson = require('../../lib/helpers/package-json');
+const logger = require('../_fixtures/logger');
+
+const defaultTypes = [
+  'dependencies',
+  'devDependencies',
+  'optionalDependencies',
+];
+
+function readFixture(fixture) {
+  return fs.readFileSync(
+    path.resolve(__dirname, `../_fixtures/package-json/${fixture}`),
+    'utf8'
+  );
+}
+
+const input01Content = readFixture('inputs/01.json');
+const input02Content = readFixture('inputs/02.json');
+
+describe('helpers/package-json', () => {
+  describe('.extractDependencies(packageJson, sections)', () => {
+    it('returns an array of correct length', () => {
+      const extractedDependencies = packageJson.extractDependencies(
+        JSON.parse(input01Content),
+        defaultTypes
+      );
+      extractedDependencies.should.be.instanceof(Array);
+      extractedDependencies.should.have.length(10);
+    });
+    it('each element contains non-null depType, depName, currentVersion', () => {
+      const extractedDependencies = packageJson.extractDependencies(
+        JSON.parse(input01Content),
+        defaultTypes
+      );
+      extractedDependencies
+        .every(dep => dep.depType && dep.depName && dep.currentVersion)
+        .should.eql(true);
+    });
+    it('supports null devDependencies', () => {
+      const extractedDependencies = packageJson.extractDependencies(
+        JSON.parse(input02Content),
+        defaultTypes
+      );
+      extractedDependencies.should.be.instanceof(Array);
+      extractedDependencies.should.have.length(6);
+    });
+  });
+  describe('.setNewValue(currentFileContent, depType, depName, newVersion, logger)', () => {
+    it('replaces a dependency value', () => {
+      const outputContent = readFixture('outputs/011.json');
+      const testContent = packageJson.setNewValue(
+        input01Content,
+        'dependencies',
+        'cheerio',
+        '0.22.1',
+        logger
+      );
+      testContent.should.equal(outputContent);
+    });
+    it('replaces only the first instance of a value', () => {
+      const outputContent = readFixture('outputs/012.json');
+      const testContent = packageJson.setNewValue(
+        input01Content,
+        'devDependencies',
+        'angular-touch',
+        '1.6.1',
+        logger
+      );
+      testContent.should.equal(outputContent);
+    });
+    it('replaces only the second instance of a value', () => {
+      const outputContent = readFixture('outputs/013.json');
+      const testContent = packageJson.setNewValue(
+        input01Content,
+        'devDependencies',
+        'angular-sanitize',
+        '1.6.1',
+        logger
+      );
+      testContent.should.equal(outputContent);
+    });
+    it('handles the case where the desired version is already supported', () => {
+      const testContent = packageJson.setNewValue(
+        input01Content,
+        'devDependencies',
+        'angular-touch',
+        '1.5.8',
+        logger
+      );
+      testContent.should.equal(input01Content);
+    });
+  });
+});
diff --git a/test/workers/__snapshots__/branch.spec.js.snap b/test/workers/__snapshots__/branch.spec.js.snap
index 61d37a092e..30e0b33d8f 100644
--- a/test/workers/__snapshots__/branch.spec.js.snap
+++ b/test/workers/__snapshots__/branch.spec.js.snap
@@ -41,3 +41,40 @@ Object {
   ],
 }
 `;
+
+exports[`workers/branch removeStandaloneBranches(upgrades) deletes standalone branch names 1`] = `
+Array [
+  Object {
+    "api": Object {
+      "deleteBranch": [Function],
+    },
+    "branchName": "what",
+    "groupBranchName": "what",
+    "logger": Object {
+      "child": [Function],
+      "debug": [Function],
+      "error": [Function],
+      "fatal": [Function],
+      "info": [Function],
+      "trace": [Function],
+      "warn": [Function],
+    },
+  },
+  Object {
+    "api": Object {
+      "deleteBranch": [Function],
+    },
+    "branchName": "what",
+    "groupBranchName": "what",
+    "logger": Object {
+      "child": [Function],
+      "debug": [Function],
+      "error": [Function],
+      "fatal": [Function],
+      "info": [Function],
+      "trace": [Function],
+      "warn": [Function],
+    },
+  },
+]
+`;
diff --git a/test/workers/__snapshots__/index.spec.js.snap b/test/workers/__snapshots__/index.spec.js.snap
new file mode 100644
index 0000000000..c4d0b2a0df
--- /dev/null
+++ b/test/workers/__snapshots__/index.spec.js.snap
@@ -0,0 +1,26 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`lib/workers/index processes repositories 1`] = `
+Array [
+  Array [
+    Object {
+      "foo": 1,
+      "repositories": Array [
+        "a",
+        "b",
+      ],
+    },
+    0,
+  ],
+  Array [
+    Object {
+      "foo": 1,
+      "repositories": Array [
+        "a",
+        "b",
+      ],
+    },
+    1,
+  ],
+]
+`;
diff --git a/test/workers/__snapshots__/repository-functions.spec.js.snap b/test/workers/__snapshots__/repository-functions.spec.js.snap
new file mode 100644
index 0000000000..1c9ad4be9d
--- /dev/null
+++ b/test/workers/__snapshots__/repository-functions.spec.js.snap
@@ -0,0 +1,95 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`workers/repository detectPackageFiles(config) adds package files to object 1`] = `
+Array [
+  "package.json",
+  "backend/package.json",
+]
+`;
+
+exports[`workers/repository groupUpgradesByBranch(upgrades, logger) does not group if different compiled branch names 1`] = `
+Object {
+  "bar-1.1.0": Array [
+    Object {
+      "branchName": "bar-{{version}}",
+      "version": "1.1.0",
+    },
+  ],
+  "foo-1.1.0": Array [
+    Object {
+      "branchName": "foo-{{version}}",
+      "version": "1.1.0",
+    },
+  ],
+  "foo-2.0.0": Array [
+    Object {
+      "branchName": "foo-{{version}}",
+      "version": "2.0.0",
+    },
+  ],
+}
+`;
+
+exports[`workers/repository groupUpgradesByBranch(upgrades, logger) groups if same compiled branch names 1`] = `
+Object {
+  "bar-1.1.0": Array [
+    Object {
+      "branchName": "bar-{{version}}",
+      "version": "1.1.0",
+    },
+  ],
+  "foo": Array [
+    Object {
+      "branchName": "foo",
+      "version": "2.0.0",
+    },
+    Object {
+      "branchName": "foo",
+      "version": "1.1.0",
+    },
+  ],
+}
+`;
+
+exports[`workers/repository groupUpgradesByBranch(upgrades, logger) groups if same compiled group name 1`] = `
+Object {
+  "foo": Array [
+    Object {
+      "branchName": "foo",
+      "version": "2.0.0",
+    },
+  ],
+  "renovate/my-group": Array [
+    Object {
+      "branchName": "bar-{{version}}",
+      "commitMessage": undefined,
+      "groupBranchName": "renovate/my-group",
+      "groupName": "My Group",
+      "groupSlug": "my-group",
+      "prBody": undefined,
+      "prTitle": undefined,
+      "version": "1.1.0",
+    },
+    Object {
+      "branchName": "foo",
+      "groupBranchName": "renovate/{{groupSlug}}",
+      "groupName": "My Group",
+      "groupSlug": "my-group",
+      "version": "1.1.0",
+    },
+  ],
+}
+`;
+
+exports[`workers/repository groupUpgradesByBranch(upgrades, logger) returns one branch if one input 1`] = `
+Object {
+  "foo-1.1.0": Array [
+    Object {
+      "branchName": "foo-{{version}}",
+      "version": "1.1.0",
+    },
+  ],
+}
+`;
+
+exports[`workers/repository initApis(config) throws if unknown platform 1`] = `"Unknown platform: foo"`;
diff --git a/test/workers/branch.spec.js b/test/workers/branch.spec.js
index a740fb4438..0174244038 100644
--- a/test/workers/branch.spec.js
+++ b/test/workers/branch.spec.js
@@ -5,13 +5,7 @@ const yarnHelper = require('../../lib/helpers/yarn');
 const defaultConfig = require('../../lib/config/defaults').getConfig();
 const packageJsonHelper = require('../../lib/helpers/package-json');
 
-const bunyan = require('bunyan');
-
-const logger = bunyan.createLogger({
-  name: 'test',
-  stream: process.stdout,
-  level: 'fatal',
-});
+const logger = require('../_fixtures/logger');
 
 jest.mock('../../lib/helpers/yarn');
 jest.mock('../../lib/helpers/package-json');
@@ -333,41 +327,85 @@ describe('workers/branch', () => {
       config.api = {
         checkForClosedPr: jest.fn(),
       };
+      config.logger = logger;
       branchWorker.ensureBranch = jest.fn(() => true);
       prWorker.ensurePr = jest.fn(() => true);
     });
     it('returns immediately if closed PR found', async () => {
       config.api.checkForClosedPr.mockReturnValue(true);
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(0);
     });
     it('does not return immediately if recreateClosed true', async () => {
       config.api.checkForClosedPr.mockReturnValue(true);
       config.recreateClosed = true;
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(1);
     });
     it('pins', async () => {
       config.upgradeType = 'pin';
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(1);
     });
     it('majors', async () => {
       config.upgradeType = 'major';
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(1);
     });
     it('minors', async () => {
       config.upgradeType = 'minor';
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(1);
     });
     it('handles errors', async () => {
       config.api.checkForClosedPr = jest.fn(() => {
         throw new Error('oops');
       });
-      await branchWorker.updateBranch([config], logger);
+      await branchWorker.updateBranch([config]);
       expect(branchWorker.ensureBranch.mock.calls.length).toBe(0);
     });
   });
+  describe('removeStandaloneBranches(upgrades)', () => {
+    it('deletes standalone branch names', async () => {
+      const api = {
+        deleteBranch: jest.fn(),
+      };
+      const upgrades = [
+        {
+          branchName: 'foo',
+          groupBranchName: 'what',
+          api,
+          logger,
+        },
+        {
+          branchName: 'bar',
+          groupBranchName: 'what',
+          api,
+          logger,
+        },
+      ];
+      await branchWorker.removeStandaloneBranches(upgrades);
+      expect(upgrades).toMatchSnapshot();
+    });
+    it('handles errors', async () => {
+      const upgrades = [
+        {
+          branchName: 'foo',
+          api: {
+            deleteBranch: jest.fn(() => {
+              throw new Error('deletion error');
+            }),
+          },
+          logger,
+        },
+        {
+          branchName: 'bar',
+          groupBranchName: 'what',
+          api: null,
+          logger,
+        },
+      ];
+      await branchWorker.removeStandaloneBranches(upgrades);
+    });
+  });
 });
diff --git a/test/workers/index.spec.js b/test/workers/index.spec.js
index 5cacbbdab3..e0a5512a3d 100644
--- a/test/workers/index.spec.js
+++ b/test/workers/index.spec.js
@@ -1,5 +1,37 @@
-require('../../lib/workers/index');
+const indexWorker = require('../../lib/workers/index');
+const repositoryWorker = require('../../lib/workers/repository');
+const configParser = require('../../lib/config');
 
-it('placeholder', () => {
-  // TODO: write tests for this module - this is here so the file shows up in coverage
+describe('lib/workers/index', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+    configParser.parseConfigs = jest.fn();
+    configParser.getRepoConfig = jest.fn();
+    repositoryWorker.processRepo = jest.fn();
+  });
+  it('handles zero repos', async () => {
+    configParser.parseConfigs.mockReturnValueOnce({
+      repositories: [],
+    });
+    await indexWorker.start();
+  });
+  it('processes repositories', async () => {
+    configParser.parseConfigs.mockReturnValueOnce({
+      foo: 1,
+      repositories: ['a', 'b'],
+    });
+    configParser.getRepoConfig.mockReturnValue({
+      repository: 'foo',
+    });
+    await indexWorker.start();
+    expect(configParser.parseConfigs.mock.calls.length).toBe(1);
+    expect(configParser.getRepoConfig.mock.calls).toMatchSnapshot();
+    expect(repositoryWorker.processRepo.mock.calls.length).toBe(2);
+  });
+  it('catches errors', async () => {
+    configParser.parseConfigs.mockImplementationOnce(() => {
+      throw new Error('a');
+    });
+    await indexWorker.start();
+  });
 });
diff --git a/test/workers/package-file.spec.js b/test/workers/package-file.spec.js
index 0fc62553f0..44d2f05920 100644
--- a/test/workers/package-file.spec.js
+++ b/test/workers/package-file.spec.js
@@ -2,13 +2,7 @@ const packageFileWorker = require('../../lib/workers/package-file');
 const npmApi = require('../../lib/api/npm');
 const versionsHelper = require('../../lib/helpers/versions');
 const packageJsonHelper = require('../../lib/helpers/package-json');
-const bunyan = require('bunyan');
-
-const logger = bunyan.createLogger({
-  name: 'test',
-  stream: process.stdout,
-  level: 'fatal',
-});
+const logger = require('../_fixtures/logger');
 
 jest.mock('../../lib/workers/branch');
 jest.mock('../../lib/workers/pr');
diff --git a/test/workers/pr.spec.js b/test/workers/pr.spec.js
index deff4e1fbc..7656ac354c 100644
--- a/test/workers/pr.spec.js
+++ b/test/workers/pr.spec.js
@@ -2,13 +2,7 @@ const prWorker = require('../../lib/workers/pr');
 const changelogHelper = require('../../lib/helpers/changelog');
 const defaultConfig = require('../../lib/config/defaults').getConfig();
 
-const bunyan = require('bunyan');
-
-const logger = bunyan.createLogger({
-  name: 'test',
-  stream: process.stdout,
-  level: 'fatal',
-});
+const logger = require('../_fixtures/logger');
 
 jest.mock('../../lib/helpers/changelog');
 changelogHelper.getChangeLog = jest.fn();
diff --git a/test/workers/repository-functions.spec.js b/test/workers/repository-functions.spec.js
new file mode 100644
index 0000000000..a948846789
--- /dev/null
+++ b/test/workers/repository-functions.spec.js
@@ -0,0 +1,343 @@
+const repositoryWorker = require('../../lib/workers/repository');
+const packageFileWorker = require('../../lib/workers/package-file');
+const branchWorker = require('../../lib/workers/branch');
+const logger = require('../_fixtures/logger');
+
+const githubApi = require('../../lib/api/github');
+const gitlabApi = require('../../lib/api/gitlab');
+const npmApi = require('../../lib/api/npm');
+
+jest.mock('../../lib/api/github');
+jest.mock('../../lib/api/gitlab');
+jest.mock('../../lib/api/npm');
+jest.mock('../../lib/workers/branch');
+jest.mock('../../lib/workers/package-file');
+
+describe('workers/repository', () => {
+  describe('setNpmrc(config)', () => {
+    it('Skips if npmrc not found', async () => {
+      const config = {
+        api: {
+          getFileContent: jest.fn(),
+        },
+      };
+      await repositoryWorker.setNpmrc(config);
+    });
+    it('Parses if npmrc found', async () => {
+      const config = {
+        api: {
+          getFileContent: jest.fn(() => 'a = b'),
+        },
+        logger,
+      };
+      await repositoryWorker.setNpmrc(config);
+    });
+    it('Catches errors', async () => {
+      const config = {
+        api: {
+          getFileContent: jest.fn(() => {
+            throw new Error('file error');
+          }),
+        },
+        logger,
+      };
+      await repositoryWorker.setNpmrc(config);
+    });
+  });
+  describe('initApis(config)', () => {
+    beforeEach(() => {
+      jest.resetAllMocks();
+    });
+    it('returns github api', async () => {
+      const config = { platform: 'github' };
+      const res = await repositoryWorker.initApis(config);
+      expect(res.platform).toEqual('github');
+      expect(githubApi.initRepo.mock.calls.length).toBe(1);
+      expect(gitlabApi.initRepo.mock.calls.length).toBe(0);
+      expect(npmApi.setNpmrc.mock.calls.length).toBe(1);
+    });
+    it('returns gitlab api', async () => {
+      const config = { platform: 'gitlab' };
+      const res = await repositoryWorker.initApis(config);
+      expect(res.platform).toEqual('gitlab');
+      expect(githubApi.initRepo.mock.calls.length).toBe(0);
+      expect(gitlabApi.initRepo.mock.calls.length).toBe(1);
+      expect(npmApi.setNpmrc.mock.calls.length).toBe(1);
+    });
+    it('throws if unknown platform', async () => {
+      const config = { platform: 'foo' };
+      let e;
+      try {
+        await repositoryWorker.initApis(config);
+      } catch (err) {
+        e = err;
+      }
+      expect(e.message).toMatchSnapshot();
+      expect(githubApi.initRepo.mock.calls.length).toBe(0);
+      expect(gitlabApi.initRepo.mock.calls.length).toBe(0);
+      expect(npmApi.setNpmrc.mock.calls.length).toBe(0);
+    });
+  });
+  describe('mergeRenovateJson(config)', () => {
+    let config;
+    beforeEach(() => {
+      config = {
+        api: {
+          getFileJson: jest.fn(),
+        },
+        logger,
+      };
+    });
+    it('returns same config if no renovate.json found', async () => {
+      expect(await repositoryWorker.mergeRenovateJson(config)).toEqual(config);
+    });
+    it('returns extended config if renovate.json found', async () => {
+      config.api.getFileJson.mockReturnValueOnce({ foo: 1 });
+      const returnConfig = await repositoryWorker.mergeRenovateJson(config);
+      expect(returnConfig.foo).toBe(1);
+      expect(returnConfig.renovateJsonPresent).toBe(true);
+    });
+  });
+  describe('onboardRepository(config)', () => {
+    let config;
+    beforeEach(() => {
+      config = {
+        api: {
+          commitFilesToBranch: jest.fn(),
+          createPr: jest.fn(() => ({ displayNumber: 1 })),
+        },
+        logger,
+      };
+    });
+    it('should commit files and create PR', async () => {
+      config.platform = 'github';
+      await repositoryWorker.onboardRepository(config);
+      expect(config.api.commitFilesToBranch.mock.calls.length).toBe(1);
+      expect(config.api.createPr.mock.calls.length).toBe(1);
+      expect(
+        config.api.createPr.mock.calls[0][2].indexOf('Pull Request')
+      ).not.toBe(-1);
+      expect(
+        config.api.createPr.mock.calls[0][2].indexOf('Merge Request')
+      ).toBe(-1);
+    });
+    it('should adapt for gitlab phrasing', async () => {
+      config.platform = 'gitlab';
+      await repositoryWorker.onboardRepository(config);
+      expect(config.api.createPr.mock.calls[0][2].indexOf('Pull Request')).toBe(
+        -1
+      );
+      expect(
+        config.api.createPr.mock.calls[0][2].indexOf('Merge Request')
+      ).not.toBe(-1);
+    });
+  });
+  describe('getOnboardingStatus(config)', () => {
+    let config;
+    beforeEach(() => {
+      config = {
+        api: {
+          commitFilesToBranch: jest.fn(),
+          createPr: jest.fn(() => ({ displayNumber: 1 })),
+          findPr: jest.fn(),
+        },
+        logger,
+      };
+    });
+    it('returns true if onboarding is false', async () => {
+      config.onboarding = false;
+      const res = await repositoryWorker.getOnboardingStatus(config);
+      expect(res).toEqual(true);
+      expect(config.api.findPr.mock.calls.length).toBe(0);
+    });
+    it('returns complete if renovate onboarded', async () => {
+      config.renovateJsonPresent = true;
+      const res = await repositoryWorker.getOnboardingStatus(config);
+      expect(res).toEqual(true);
+      expect(config.api.findPr.mock.calls.length).toBe(0);
+    });
+    it('returns complete if pr and pr is closed', async () => {
+      config.api.findPr.mockReturnValueOnce({ isClosed: true });
+      const res = await repositoryWorker.getOnboardingStatus(config);
+      expect(res).toEqual(true);
+      expect(config.api.findPr.mock.calls.length).toBe(1);
+    });
+    it('returns in progres if pr and pr is not closed', async () => {
+      config.api.findPr.mockReturnValueOnce({});
+      const res = await repositoryWorker.getOnboardingStatus(config);
+      expect(res).toEqual(false);
+      expect(config.api.findPr.mock.calls.length).toBe(1);
+    });
+    it('returns none if no pr', async () => {
+      const res = await repositoryWorker.getOnboardingStatus(config);
+      expect(res).toEqual(false);
+      expect(config.api.findPr.mock.calls.length).toBe(1);
+    });
+  });
+  describe('detectPackageFiles(config)', () => {
+    it('adds package files to object', async () => {
+      const config = {
+        api: {
+          findFilePaths: jest.fn(() => [
+            'package.json',
+            'backend/package.json',
+          ]),
+        },
+        logger,
+      };
+      const res = await repositoryWorker.detectPackageFiles(config);
+      expect(res).toMatchObject(config);
+      expect(res.packageFiles).toMatchSnapshot();
+    });
+  });
+  describe('determineRepoUpgrades(config)', () => {
+    let config;
+    beforeEach(() => {
+      config = {
+        logger,
+      };
+    });
+    it('returns empty array if no packageFiles', async () => {
+      config.packageFiles = [];
+      const upgrades = await repositoryWorker.determineRepoUpgrades(config);
+      expect(upgrades.length).toBe(0);
+    });
+    it('returns empty array if none found', async () => {
+      config.packageFiles = [
+        'package.json',
+        {
+          packageFile: 'backend/package.json',
+        },
+      ];
+      packageFileWorker.processPackageFile.mockReturnValue([]);
+      const upgrades = await repositoryWorker.determineRepoUpgrades(config);
+      expect(upgrades.length).toBe(0);
+    });
+    it('returns array if upgrades found', async () => {
+      config.packageFiles = [
+        'package.json',
+        {
+          packageFile: 'backend/package.json',
+        },
+        {
+          fileName: 'frontend/package.json',
+        },
+      ];
+      packageFileWorker.processPackageFile.mockReturnValueOnce(['a']);
+      packageFileWorker.processPackageFile.mockReturnValueOnce(['b', 'c']);
+      const upgrades = await repositoryWorker.determineRepoUpgrades(config);
+      expect(upgrades.length).toBe(3);
+    });
+  });
+  describe('groupUpgradesByBranch(upgrades, logger)', () => {
+    it('returns empty object if no input array', async () => {
+      const res = await repositoryWorker.groupUpgradesByBranch([], logger);
+      expect(res).toEqual({});
+    });
+    it('returns one branch if one input', async () => {
+      const upgrades = [
+        {
+          branchName: 'foo-{{version}}',
+          version: '1.1.0',
+        },
+      ];
+      const res = await repositoryWorker.groupUpgradesByBranch(
+        upgrades,
+        logger
+      );
+      expect(Object.keys(res).length).toBe(1);
+      expect(res).toMatchSnapshot();
+    });
+    it('does not group if different compiled branch names', async () => {
+      const upgrades = [
+        {
+          branchName: 'foo-{{version}}',
+          version: '1.1.0',
+        },
+        {
+          branchName: 'foo-{{version}}',
+          version: '2.0.0',
+        },
+        {
+          branchName: 'bar-{{version}}',
+          version: '1.1.0',
+        },
+      ];
+      const res = await repositoryWorker.groupUpgradesByBranch(
+        upgrades,
+        logger
+      );
+      expect(Object.keys(res).length).toBe(3);
+      expect(res).toMatchSnapshot();
+    });
+    it('groups if same compiled branch names', async () => {
+      const upgrades = [
+        {
+          branchName: 'foo',
+          version: '1.1.0',
+        },
+        {
+          branchName: 'foo',
+          version: '2.0.0',
+        },
+        {
+          branchName: 'bar-{{version}}',
+          version: '1.1.0',
+        },
+      ];
+      const res = await repositoryWorker.groupUpgradesByBranch(
+        upgrades,
+        logger
+      );
+      expect(Object.keys(res).length).toBe(2);
+      expect(res).toMatchSnapshot();
+    });
+    it('groups if same compiled group name', async () => {
+      const upgrades = [
+        {
+          branchName: 'foo',
+          version: '1.1.0',
+          groupName: 'My Group',
+          groupBranchName: 'renovate/{{groupSlug}}',
+        },
+        {
+          branchName: 'foo',
+          version: '2.0.0',
+        },
+        {
+          branchName: 'bar-{{version}}',
+          version: '1.1.0',
+          groupName: 'My Group',
+          groupBranchName: 'renovate/my-group',
+        },
+      ];
+      const res = await repositoryWorker.groupUpgradesByBranch(
+        upgrades,
+        logger
+      );
+      expect(Object.keys(res).length).toBe(2);
+      expect(res).toMatchSnapshot();
+    });
+  });
+  describe('updateBranchesSequentially(branchUpgrades, logger)', () => {
+    beforeEach(() => {
+      jest.resetAllMocks();
+    });
+    it('handles empty case', async () => {
+      await repositoryWorker.updateBranchesSequentially({}, logger);
+      expect(branchWorker.updateBranch.mock.calls.length).toBe(0);
+    });
+    it('updates branches', async () => {
+      const branchUpgrades = {
+        foo: {},
+        bar: {},
+        baz: {},
+      };
+      await repositoryWorker.updateBranchesSequentially(branchUpgrades, logger);
+      expect(branchWorker.updateBranch.mock.calls.length).toBe(3);
+    });
+  });
+  describe('processRepo(repoConfig)', () => {
+    // TODO
+  });
+});
diff --git a/test/workers/repository.spec.js b/test/workers/repository.spec.js
index 2babfd8c83..b79da639bb 100644
--- a/test/workers/repository.spec.js
+++ b/test/workers/repository.spec.js
@@ -1,28 +1,33 @@
 const repositoryWorker = require('../../lib/workers/repository');
-const branchWorker = require('../../lib/workers/branch');
+const logger = require('../_fixtures/logger');
 
-jest.mock('../../lib/workers/branch');
-jest.mock('../../lib/workers/pr');
-jest.mock('../../lib/api/npm');
-jest.mock('../../lib/api/github');
-jest.mock('../../lib/api/gitlab');
-jest.mock('../../lib/helpers/versions');
+repositoryWorker.initApis = jest.fn(input => input);
+repositoryWorker.mergeRenovateJson = jest.fn(input => input);
+repositoryWorker.getOnboardingStatus = jest.fn(() => true);
+repositoryWorker.detectPackageFiles = jest.fn(input => input);
 
-describe('repositoryWorker', () => {
-  describe('processUpgrades(upgrades)', () => {
+describe('workers/repository', () => {
+  describe('processRepo', () => {
+    let config;
     beforeEach(() => {
-      repositoryWorker.updateBranch = jest.fn();
+      config = {
+        logger,
+        packageFiles: [],
+      };
     });
-    it('handles zero upgrades', async () => {
-      // await repositoryWorker.processUpgrades([]);
-      expect(branchWorker.updateBranch.mock.calls.length).toBe(0);
+    it('returns early if repo is not onboarded', async () => {
+      repositoryWorker.getOnboardingStatus.mockReturnValueOnce(false);
+      await repositoryWorker.processRepo(config);
     });
-    it('handles non-zero upgrades', async () => {
-      await repositoryWorker.processUpgrades([
-        { branchName: 'a' },
-        { branchName: 'b' },
-      ]);
-      expect(branchWorker.updateBranch.mock.calls.length).toBe(2);
+    it('detects package files if none configured', async () => {
+      repositoryWorker.getOnboardingStatus.mockReturnValueOnce(true);
+      await repositoryWorker.processRepo(config);
+    });
+    it('swallows errors', async () => {
+      repositoryWorker.initApis.mockImplementationOnce(() => {
+        throw new Error('bad init');
+      });
+      await repositoryWorker.processRepo(config);
     });
   });
 });
diff --git a/yarn.lock b/yarn.lock
index a7d4a43e99..290e577391 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2593,10 +2593,14 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
   dependencies:
     minimist "0.0.8"
 
-moment@2.17.1, moment@2.x.x, moment@^2.10.6:
+moment@2.17.1:
   version "2.17.1"
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.17.1.tgz#fed9506063f36b10f066c8b59a144d7faebe1d82"
 
+moment@2.x.x, moment@^2.10.6:
+  version "2.18.1"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
+
 ms@2.0.0, ms@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -3058,8 +3062,8 @@ read-pkg@^2.0.0:
     path-type "^2.0.0"
 
 readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.2.tgz#5a04df05e4f57fe3f0dc68fdd11dc5c97c7e6f4d"
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.1.tgz#84e26965bb9e785535ed256e8d38e92c69f09d10"
   dependencies:
     core-util-is "~1.0.0"
     inherits "~2.0.3"
@@ -3290,6 +3294,10 @@ safe-buffer@^5.0.1, safe-buffer@~5.1.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
 
+safe-buffer@~5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
+
 safe-json-stringify@~1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz#81a098f447e4bbc3ff3312a243521bc060ef5911"
@@ -3307,8 +3315,8 @@ sane@~1.6.0:
     watch "~0.10.0"
 
 sax@^1.2.1:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
 
 semver-diff@^2.0.0:
   version "2.1.0"
@@ -3480,10 +3488,10 @@ string-width@^2.0.0:
     strip-ansi "^3.0.0"
 
 string_decoder@~1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.2.tgz#b29e1f4e1125fa97a10382b8a533737b7491e179"
   dependencies:
-    safe-buffer "~5.1.0"
+    safe-buffer "~5.0.1"
 
 stringstream@~0.0.4:
   version "0.0.5"
-- 
GitLab