diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index bf73c78ba023d79260dbeae7214001da7d459b36..7f7865dfbd2a19be67ed7bd6bfb0d76054f18241 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -1652,6 +1652,21 @@ const options = [
     mergeable: true,
     cli: false,
   },
+  {
+    name: 'homebrew',
+    releaseStatus: 'beta',
+    description: 'Configuration object for homebrew',
+    stage: 'package',
+    type: 'object',
+    default: {
+      enabled: true,
+      commitMessageTopic: 'Homebrew Formula {{depName}}',
+      managerBranchPrefix: 'homebrew-',
+      fileMatch: ['^Formula/[^/]+[.]rb$'],
+    },
+    mergeable: true,
+    cli: false,
+  },
   {
     name: 'hostRules',
     description: 'Host rules/configuration including credentials',
diff --git a/lib/manager/homebrew/extract.js b/lib/manager/homebrew/extract.js
new file mode 100644
index 0000000000000000000000000000000000000000..acd417bcd1126f51b62ab060882ee8ca682ea5bc
--- /dev/null
+++ b/lib/manager/homebrew/extract.js
@@ -0,0 +1,207 @@
+const semver = require('../../versioning/semver');
+const { skip, isSpace, removeComments } = require('./util');
+
+module.exports = {
+  extractPackageFile,
+  parseUrlPath,
+};
+
+// TODO: Maybe check if quotes/double-quotes are balanced
+function extractPackageFile(content) {
+  logger.trace('extractPackageFile()');
+  /*
+    1. match "class className < Formula"
+    2. extract className
+    3. extract url field (get depName from url)
+    4. extract sha256 field
+  */
+  const cleanContent = removeComments(content);
+  const className = extractClassName(cleanContent);
+  if (!className) {
+    logger.debug('Invalid class definition');
+    return null;
+  }
+  const url = extractUrl(cleanContent);
+  if (!url) {
+    logger.debug('Invalid URL field');
+  }
+  const urlPathResult = parseUrlPath(url);
+  let skipReason;
+  let currentValue = null;
+  let ownerName = null;
+  let repoName = null;
+  if (urlPathResult) {
+    currentValue = urlPathResult.currentValue;
+    ownerName = urlPathResult.ownerName;
+    repoName = urlPathResult.repoName;
+  } else {
+    logger.debug('Error: Unsupported URL field');
+    skipReason = 'unsupported-url';
+  }
+  const sha256 = extractSha256(cleanContent);
+  if (!sha256 || sha256.length !== 64) {
+    logger.debug('Error: Invalid sha256 field');
+    skipReason = 'invalid-sha256';
+  }
+  const dep = {
+    depName: `${ownerName}/${repoName}`,
+    url,
+    sha256,
+    ownerName,
+    repoName,
+    currentValue,
+    datasource: 'github',
+  };
+  if (skipReason) {
+    dep.skipReason = skipReason;
+    if (skipReason === 'unsupported-url') {
+      dep.depName = className;
+      dep.datasource = null;
+    }
+  }
+  const deps = [dep];
+  return { deps };
+}
+
+function extractSha256(content) {
+  const sha256RegExp = /(^|\s)sha256(\s)/;
+  let i = content.search(sha256RegExp);
+  if (isSpace(content[i])) {
+    i += 1;
+  }
+  return parseSha256(i, content);
+}
+
+function parseSha256(idx, content) {
+  let i = idx;
+  i += 'sha256'.length;
+  i = skip(i, content, c => {
+    return isSpace(c);
+  });
+  if (content[i] !== '"' && content[i] !== "'") {
+    return null;
+  }
+  i += 1;
+  let j = i;
+  j = skip(i, content, c => {
+    return c !== '"' && c !== "'";
+  });
+  const sha256 = content.slice(i, j);
+  return sha256;
+}
+
+function extractUrl(content) {
+  const urlRegExp = /(^|\s)url(\s)/;
+  let i = content.search(urlRegExp);
+  // content.search() returns -1 if not found
+  if (i === -1) {
+    return null;
+  }
+  /* istanbul ignore else */
+  if (isSpace(content[i])) {
+    i += 1;
+  }
+  return parseUrl(i, content);
+}
+
+function parseUrlPath(urlStr) {
+  if (!urlStr) {
+    return null;
+  }
+  try {
+    const url = new URL(urlStr);
+    if (url.hostname !== 'github.com') {
+      return null;
+    }
+    let s = url.pathname.split('/');
+    s = s.filter(val => val);
+    const ownerName = s[0];
+    const repoName = s[1];
+    let currentValue;
+    if (s[2] === 'archive') {
+      currentValue = s[3];
+      const targz = currentValue.slice(
+        currentValue.length - 7,
+        currentValue.length
+      );
+      if (targz === '.tar.gz') {
+        currentValue = currentValue.substring(0, currentValue.length - 7);
+      }
+    } else if (s[2] === 'releases' && s[3] === 'download') {
+      currentValue = s[4];
+    }
+    if (!currentValue) {
+      return null;
+    }
+    if (!semver.isValid(currentValue)) {
+      return null;
+    }
+    return { currentValue, ownerName, repoName };
+  } catch (_) {
+    return null;
+  }
+}
+
+function parseUrl(idx, content) {
+  let i = idx;
+  i += 'url'.length;
+  i = skip(i, content, c => {
+    return isSpace(c);
+  });
+  const chr = content[i];
+  if (chr !== '"' && chr !== "'") {
+    return null;
+  }
+  i += 1;
+  let j = i;
+  j = skip(i, content, c => {
+    return c !== '"' && c !== "'" && !isSpace(c);
+  });
+  const url = content.slice(i, j);
+  return url;
+}
+
+function extractClassName(content) {
+  const classRegExp = /(^|\s)class\s/;
+  let i = content.search(classRegExp);
+  if (isSpace(content[i])) {
+    i += 1;
+  }
+  return parseClassHeader(i, content);
+}
+
+/* This function parses the "class className < Formula" header
+   and returns the className and index of the character just after the header */
+function parseClassHeader(idx, content) {
+  let i = idx;
+  i += 'class'.length;
+  i = skip(i, content, c => {
+    return isSpace(c);
+  });
+  // Skip all non space and non '<' characters
+  let j = skip(i, content, c => {
+    return !isSpace(c) && c !== '<';
+  });
+  const className = content.slice(i, j);
+  i = j;
+  // Skip spaces
+  i = skip(i, content, c => {
+    return isSpace(c);
+  });
+  if (content[i] === '<') {
+    i += 1;
+  } else {
+    return null;
+  } // Skip spaces
+  i = skip(i, content, c => {
+    return isSpace(c);
+  });
+  // Skip non-spaces
+  j = skip(i, content, c => {
+    return !isSpace(c);
+  });
+  if (content.slice(i, j) !== 'Formula') {
+    return null;
+  }
+  return className;
+}
diff --git a/lib/manager/homebrew/index.js b/lib/manager/homebrew/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..749cae6f4db0ec27bb7cde5845780752ce144ac9
--- /dev/null
+++ b/lib/manager/homebrew/index.js
@@ -0,0 +1,7 @@
+const { extractPackageFile } = require('./extract');
+const { updateDependency } = require('./update');
+
+module.exports = {
+  extractPackageFile,
+  updateDependency,
+};
diff --git a/lib/manager/homebrew/update.js b/lib/manager/homebrew/update.js
new file mode 100644
index 0000000000000000000000000000000000000000..201823405ea6bea8d36bf78512eec673c827be8d
--- /dev/null
+++ b/lib/manager/homebrew/update.js
@@ -0,0 +1,183 @@
+const got = require('got');
+const crypto = require('crypto');
+const { coerce } = require('semver');
+const { parseUrlPath } = require('./extract');
+const { skip, isSpace, removeComments } = require('./util');
+
+module.exports = {
+  updateDependency,
+};
+
+// TODO: Refactor
+async function updateDependency(content, upgrade) {
+  logger.trace('updateDependency()');
+  /*
+    1. Update url field
+    2. Update sha256 field
+   */
+  let newContent = content;
+  let newUrl;
+  let file;
+  // Example urls:
+  // "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+  // "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz"
+  const oldParsedUrlPath = parseUrlPath(upgrade.url);
+  if (!oldParsedUrlPath) {
+    logger.debug(
+      `Failed to update - upgrade.url is invalid ${upgrade.depName}`
+    );
+    return content;
+  }
+  try {
+    newUrl = `https://github.com/${upgrade.ownerName}/${
+      upgrade.repoName
+    }/releases/download/${upgrade.newValue}/${upgrade.repoName}-${coerce(
+      upgrade.newValue
+    )}.tar.gz`;
+    file = (await got(newUrl, { encoding: null })).body;
+  } catch (errOuter) {
+    logger.debug(
+      `Failed to download release download for ${
+        upgrade.depName
+      } - trying archive instead`
+    );
+    try {
+      newUrl = `https://github.com/${upgrade.ownerName}/${
+        upgrade.repoName
+      }/archive/${upgrade.newValue}.tar.gz`;
+      file = (await got(newUrl, { encoding: null })).body;
+    } catch (errInner) {
+      logger.debug(
+        `Failed to download archive download for ${
+          upgrade.depName
+        } - update failed`
+      );
+      return content;
+    }
+  }
+  const newParsedUrlPath = parseUrlPath(newUrl);
+  if (!newParsedUrlPath) {
+    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
+    return content;
+  }
+  if (upgrade.newValue !== newParsedUrlPath.currentValue) {
+    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
+    return content;
+  }
+  const newSha256 = crypto
+    .createHash('sha256')
+    .update(file)
+    .digest('hex');
+  newContent = updateUrl(content, upgrade.url, newUrl);
+  if (!newContent) {
+    logger.debug(`Failed to update url for dependency ${upgrade.depName}`);
+    return content;
+  }
+  newContent = updateSha256(newContent, upgrade.sha256, newSha256);
+  if (!newContent) {
+    logger.debug(`Failed to update sha256 for dependency ${upgrade.depName}`);
+    return content;
+  }
+  return newContent;
+}
+
+function updateUrl(content, oldUrl, newUrl) {
+  const urlRegExp = /(^|\s)url(\s)/;
+  let i = content.search(urlRegExp);
+  if (i === -1) {
+    return null;
+  }
+  if (isSpace(content[i])) {
+    i += 1;
+  }
+  let newContent = replaceUrl(i, content, oldUrl, newUrl);
+  const testContent = getUrlTestContent(content, oldUrl, newUrl);
+  if (!newContent || !testContent) {
+    return null;
+  }
+  while (removeComments(newContent) !== testContent) {
+    i += 'url'.length;
+    i += content.substring(i).search(urlRegExp);
+    if (isSpace(content[i])) {
+      i += 1;
+    }
+    newContent = replaceUrl(i, content, oldUrl, newUrl);
+  }
+  return newContent;
+}
+
+function getUrlTestContent(content, oldUrl, newUrl) {
+  const urlRegExp = /(^|\s)url(\s)/;
+  const cleanContent = removeComments(content);
+  let j = cleanContent.search(urlRegExp);
+  if (isSpace(cleanContent[j])) {
+    j += 1;
+  }
+  const testContent = replaceUrl(j, cleanContent, oldUrl, newUrl);
+  return testContent;
+}
+
+function replaceUrl(idx, content, oldUrl, newUrl) {
+  let i = idx;
+  i += 'url'.length;
+  i = skip(i, content, c => isSpace(c));
+  const chr = content[i];
+  if (chr !== '"' && chr !== "'") {
+    return null;
+  }
+  i += 1;
+  const newContent =
+    content.substring(0, i) + content.substring(i).replace(oldUrl, newUrl);
+  return newContent;
+}
+
+function updateSha256(content, oldSha256, newSha256) {
+  const sha256RegExp = /(^|\s)sha256(\s)/;
+  let i = content.search(sha256RegExp);
+  if (i === -1) {
+    return null;
+  }
+  if (isSpace(content[i])) {
+    i += 1;
+  }
+  let newContent = replaceSha256(i, content, oldSha256, newSha256);
+  const testContent = getSha256TestContent(content, oldSha256, newSha256);
+  if (!newContent || !testContent) {
+    return null;
+  }
+  while (removeComments(newContent) !== testContent) {
+    i += 'sha256'.length;
+    i += content.substring(i).search(sha256RegExp);
+    if (isSpace(content[i])) {
+      i += 1;
+    }
+    newContent = replaceSha256(i, content, oldSha256, newSha256);
+  }
+  return newContent;
+}
+
+function getSha256TestContent(content, oldSha256, newSha256) {
+  const sha256RegExp = /(^|\s)sha256(\s)/;
+  const cleanContent = removeComments(content);
+  let j = cleanContent.search(sha256RegExp);
+  if (isSpace(cleanContent[j])) {
+    j += 1;
+  }
+  const testContent = replaceSha256(j, cleanContent, oldSha256, newSha256);
+  return testContent;
+}
+
+function replaceSha256(idx, content, oldSha256, newSha256) {
+  let i = idx;
+  i += 'sha256'.length;
+  i = skip(i, content, c => isSpace(c));
+  const chr = content[i];
+  if (chr !== '"' && chr !== "'") {
+    return null;
+  }
+  i += 1;
+  const newContent =
+    content.substring(0, i) +
+    content.substring(i).replace(oldSha256, newSha256);
+  return newContent;
+}
diff --git a/lib/manager/homebrew/util.js b/lib/manager/homebrew/util.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb52b1af02b2240d226b802a180a33a36b6af595
--- /dev/null
+++ b/lib/manager/homebrew/util.js
@@ -0,0 +1,69 @@
+module.exports = {
+  skip,
+  isSpace,
+  removeComments,
+};
+
+function skip(idx, content, cond) {
+  let i = idx;
+  while (i < content.length) {
+    if (!cond(content[i])) {
+      return i;
+    }
+    i += 1;
+  }
+  return i;
+}
+
+function isSpace(c) {
+  return /\s/.test(c);
+}
+
+function removeComments(content) {
+  let newContent = removeLineComments(content);
+  newContent = removeMultiLineComments(newContent);
+  return newContent;
+}
+
+// Remove line comments starting with #
+function removeLineComments(content) {
+  let newContent = '';
+  let comment = false;
+  for (let i = 0; i < content.length; i += 1) {
+    const c = content[i];
+    if (c === '#') {
+      comment = true;
+    }
+    if (comment) {
+      if (c === '\n') {
+        comment = false;
+      }
+    }
+    if (!comment) {
+      newContent += c;
+    }
+  }
+  return newContent;
+}
+
+// Remove multi-line comments enclosed between =begin and =end
+function removeMultiLineComments(content) {
+  const beginRegExp = /(^|\n)=begin\s/;
+  const endRegExp = /(^|\n)=end\s/;
+  let newContent = content;
+  let i = newContent.search(beginRegExp);
+  let j = newContent.search(endRegExp);
+  while (i !== -1 && j !== -1) {
+    if (newContent[i] === '\n') {
+      i += 1;
+    }
+    if (newContent[j] === '\n') {
+      j += 1;
+    }
+    j += '=end'.length;
+    newContent = newContent.substring(0, i) + newContent.substring(j);
+    i = newContent.search(beginRegExp);
+    j = newContent.search(endRegExp);
+  }
+  return newContent;
+}
diff --git a/lib/manager/index.js b/lib/manager/index.js
index 6f0c41e100d961ac258307fbfebc0ce195c29b58..dd21164895812dcc45c3dfbb57b2345ddfd148ec 100644
--- a/lib/manager/index.js
+++ b/lib/manager/index.js
@@ -28,6 +28,7 @@ const managerList = [
   'terraform',
   'travis',
   'ruby-version',
+  'homebrew',
 ];
 const managers = {};
 for (const manager of managerList) {
diff --git a/renovate-schema.json b/renovate-schema.json
index ac1b83caaa6d3b969075c5da7dc6e13a00f6acbc..b869e7581f8c1f1d1a6e4649eb3a008a51ba155a 100644
--- a/renovate-schema.json
+++ b/renovate-schema.json
@@ -1136,6 +1136,17 @@
       },
       "$ref": "#"
     },
+    "homebrew": {
+      "description": "Configuration object for homebrew",
+      "type": "object",
+      "default": {
+        "enabled": true,
+        "commitMessageTopic": "Homebrew Formula {{depName}}",
+        "managerBranchPrefix": "homebrew-",
+        "fileMatch": ["^Formula/[^/]+[.]rb$"]
+      },
+      "$ref": "#"
+    },
     "hostRules": {
       "description": "Host rules/configuration including credentials",
       "type": "array"
diff --git a/test/config/__snapshots__/validation.spec.js.snap b/test/config/__snapshots__/validation.spec.js.snap
index e54d4067e587428af702c98c2b2bacd0f7ba4619..925298d2d17c4232df4f98ca35edb99c2ff3ad76 100644
--- a/test/config/__snapshots__/validation.spec.js.snap
+++ b/test/config/__snapshots__/validation.spec.js.snap
@@ -87,7 +87,7 @@ Array [
     "depName": "Configuration Error",
     "message": "packageRules:
         You have included an unsupported manager in a package rule. Your list: foo. 
-        Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, terraform, travis, ruby-version).",
+        Supported managers are: (ansible, bazel, buildkite, bundler, cargo, circleci, composer, docker-compose, dockerfile, github-actions, gitlabci, gomod, gradle, gradle-wrapper, kubernetes, maven, meteor, npm, nuget, nvm, pip_requirements, pip_setup, pipenv, poetry, pub, sbt, terraform, travis, ruby-version, homebrew).",
   },
 ]
 `;
diff --git a/test/manager/homebrew/__snapshots__/extract.spec.js.snap b/test/manager/homebrew/__snapshots__/extract.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..b0f7e161fa00f980eb49e9caf074efd47cfb7f9b
--- /dev/null
+++ b/test/manager/homebrew/__snapshots__/extract.spec.js.snap
@@ -0,0 +1,219 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`lib/manager/homebrew/extract extractPackageFile() extracts "archive" github dependency 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "v0.8.2",
+      "datasource": "github",
+      "depName": "bazelbuild/bazel-watcher",
+      "ownerName": "bazelbuild",
+      "repoName": "bazel-watcher",
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() extracts "releases" github dependency 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "v0.16.1",
+      "datasource": "github",
+      "depName": "aide/aide",
+      "ownerName": "aide",
+      "repoName": "aide",
+      "sha256": "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7",
+      "url": "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() handles no space before class header 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "v0.8.2",
+      "datasource": "github",
+      "depName": "bazelbuild/bazel-watcher",
+      "ownerName": "bazelbuild",
+      "repoName": "bazel-watcher",
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips github dependency with wrong format 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Acmetool",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "6f2cf5cfb987a2df2f791c162209039804fd8fd12692da69f52153ec9668e9ca",
+      "skipReason": "unsupported-url",
+      "url": "https://github.com/hlandau/acme.git",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Ibazel",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "skipReason": "unsupported-url",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.not_tar.not_gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 2`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Ibazel",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "skipReason": "unsupported-url",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/vInvalid.version.2.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 3`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Ibazel",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "skipReason": "unsupported-url",
+      "url": null,
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if invalid url field 4`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Ibazel",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "skipReason": "unsupported-url",
+      "url": "invalid_url",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if sha256 field is invalid 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "v0.8.2",
+      "datasource": "github",
+      "depName": "bazelbuild/bazel-watcher",
+      "ownerName": "bazelbuild",
+      "repoName": "bazel-watcher",
+      "sha256": "26f5125218fad2741d3caf937b0229",
+      "skipReason": "invalid-sha256",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if there is no sha256 field 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "v0.8.2",
+      "datasource": "github",
+      "depName": "bazelbuild/bazel-watcher",
+      "ownerName": "bazelbuild",
+      "repoName": "bazel-watcher",
+      "sha256": null,
+      "skipReason": "invalid-sha256",
+      "url": "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips if there is no url field 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Ibazel",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4",
+      "skipReason": "unsupported-url",
+      "url": null,
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips sourceforge dependency 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Aalib",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "fbddda9230cf6ee2a4f5706b4b11e2190ae45f5eda1f0409dc4f99b35e0a70ee",
+      "skipReason": "unsupported-url",
+      "url": "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz",
+    },
+  ],
+}
+`;
+
+exports[`lib/manager/homebrew/extract extractPackageFile() skips sourceforge dependency 2`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": null,
+      "datasource": null,
+      "depName": "Aap",
+      "ownerName": null,
+      "repoName": null,
+      "sha256": "3f53b2fc277756042449416150acc477f29de93692944f8a77e8cef285a1efd8",
+      "skipReason": "unsupported-url",
+      "url": "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip",
+    },
+  ],
+}
+`;
diff --git a/test/manager/homebrew/__snapshots__/update.spec.js.snap b/test/manager/homebrew/__snapshots__/update.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..c307d2d924d56960b4a46a04cb32e122f499e964
--- /dev/null
+++ b/test/manager/homebrew/__snapshots__/update.spec.js.snap
@@ -0,0 +1,115 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`manager/homebrew/update updates "archive" github dependency 1`] = `
+"# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the \\"License\\");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an \\"AS IS\\" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+=begin
+  url \\"https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz\\"
+  sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+=end
+# url \\"https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz\\"
+# sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+
+$sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4';
+class Ibazel < Formula
+  desc 'IBazel is a tool for building Bazel targets when source files change.'
+  homepage 'https://github.com/bazelbuild/bazel-watcher'
+  url \\"https://github.com/bazelbuild/bazel-watcher/releases/download/v0.9.3/bazel-watcher-0.9.3.tar.gz\\"
+
+  # To generate run:
+  # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum
+  sha256 '9c96cc68155bd283282123413ecaafe52b12ea5f2585c3c383ce6141f779a58f'
+
+  bottle :unneeded
+
+  depends_on \\"bazelbuild/tap/bazel\\" => :build
+
+  def install
+    system 'bazel', 'build', '--config=release', '--verbose_failures', '--experimental_platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64', '//ibazel:ibazel'
+    bin.install 'bazel-bin/ibazel/darwin_amd64_pure_stripped/ibazel' => 'ibazel'
+  end
+
+  test do
+    # Since ibazel loops in most cases the quickest check of valididty
+    # I can think of is to get the version output which happens when
+    # invoked without any arguments.
+    system bin / 'ibazel'
+  end
+end
+"
+`;
+
+exports[`manager/homebrew/update updates "releases" github dependency 1`] = `
+"=begin
+  url \\"https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz\\"
+  sha256 \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\"
+=end
+# url \\"https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz\\"
+# sha256 \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\"
+
+$sha256 = \\"0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7\\"
+class Aide < Formula
+  desc \\"File and directory integrity checker\\"
+  homepage \\"https://aide.github.io/\\"
+  url \\"https://github.com/aide/aide/releases/download/v0.17.7/aide-0.17.7.tar.gz\\"
+  sha256 \\"337c78a56c8dde1a42eb767bf89f16eb3ee4207e4817954899a3f0f293c6ad6b\\"
+
+  bottle do
+    cellar :any
+    sha256 \\"53b1dfabc76d6e54db56ec24f7f91b6cc9dcdd18210d17d2df92f86225fb9c9f\\" => :mojave
+    sha256 \\"79a2d4ce92526516891c844a4852161d39421f9dc31d2eba5ea0e48d79496053\\" => :high_sierra
+    sha256 \\"b626fcf7e52a0ea66fbed58bdc00cb08484f7bce8e84e61edf6740fbad7fabc5\\" => :sierra
+  end
+
+  head do
+    url \\"https://github.com/aide/aide.git\\"
+    depends_on \\"autoconf\\" => :build
+    depends_on \\"automake\\" => :build
+  end
+
+  depends_on \\"libgcrypt\\"
+  depends_on \\"libgpg-error\\"
+  depends_on \\"pcre\\"
+
+  def install
+    system \\"sh\\", \\"./autogen.sh\\" if build.head?
+
+    system \\"./configure\\", \\"--disable-lfs\\",
+                          \\"--disable-static\\",
+                          \\"--with-curl\\",
+                          \\"--with-zlib\\",
+                          \\"--sysconfdir=#{etc}\\",
+                          \\"--prefix=#{prefix}\\"
+
+    system \\"make\\", \\"install\\"
+  end
+
+  test do
+    (testpath/\\"aide.conf\\").write <<~EOS
+      database = file:/var/lib/aide/aide.db
+      database_out = file:/var/lib/aide/aide.db.new
+      database_new = file:/var/lib/aide/aide.db.new
+      gzip_dbout = yes
+      summarize_changes = yes
+      grouped = yes
+      verbose = 7
+      database_attrs = sha256
+      /etc p+i+u+g+sha256
+    EOS
+    system \\"#{bin}/aide\\", \\"--config-check\\", \\"-c\\", \\"aide.conf\\"
+  end
+end
+"
+`;
diff --git a/test/manager/homebrew/_fixtures/aalib.rb b/test/manager/homebrew/_fixtures/aalib.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3532c35eb2bd182eee404f47716e8168e8c8aabf
--- /dev/null
+++ b/test/manager/homebrew/_fixtures/aalib.rb
@@ -0,0 +1,45 @@
+=begin
+  url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz"
+=end
+# url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz"
+
+class Aalib < Formula
+  desc "Portable ASCII art graphics library"
+  homepage "https://aa-project.sourceforge.io/aalib/"
+  url "https://downloads.sourceforge.net/aa-project/aalib-1.4rc5.tar.gz"
+  sha256 "fbddda9230cf6ee2a4f5706b4b11e2190ae45f5eda1f0409dc4f99b35e0a70ee"
+  revision 1
+
+  bottle do
+    cellar :any_skip_relocation
+    sha256 "a19ebbf86362d9a90900dd0b4013ebed778cd8681c0b3ed122c8bbaa04b11cbe" => :mojave
+    sha256 "b2c5467ff9182645676381967b8dc89878f88900b19bed34ef432fd3257aa2a0" => :high_sierra
+    sha256 "2c2d05720ca991422e4c27e3f770c29024c5197871cba67404f4e72a3cfaf002" => :sierra
+    sha256 "9b3f19e5da28fb682aeb1fe40f1747d1b532490dd50262978aaefcb7afbc8804" => :el_capitan
+    sha256 "9e08dd4e3545b05353f3158e4e756c20a301bef295b72183e1fd5fb1d6d8e897" => :yosemite
+  end
+
+  # Fix malloc/stdlib issue on macOS
+  # Fix underquoted definition of AM_PATH_AALIB in aalib.m4
+  patch do
+    url "https://raw.githubusercontent.com/Homebrew/formula-patches/6e23dfb/aalib/1.4rc5.patch"
+    sha256 "54aeff2adaea53902afc2660afb9534675b3ea522c767cbc24a5281080457b2c"
+  end
+
+  def install
+    ENV.ncurses_define
+    system "./configure", "--disable-debug",
+                          "--disable-dependency-tracking",
+                          "--prefix=#{prefix}",
+                          "--mandir=#{man}",
+                          "--infodir=#{info}",
+                          "--enable-shared=yes",
+                          "--enable-static=yes",
+                          "--without-x"
+    system "make", "install"
+  end
+
+  test do
+    system "script", "-q", "/dev/null", bin/"aainfo"
+  end
+end
diff --git a/test/manager/homebrew/_fixtures/aap.rb b/test/manager/homebrew/_fixtures/aap.rb
new file mode 100644
index 0000000000000000000000000000000000000000..486d648185da13bb9760424eccd82f07467bca00
--- /dev/null
+++ b/test/manager/homebrew/_fixtures/aap.rb
@@ -0,0 +1,35 @@
+=begin
+url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip"
+=end
+# url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip"
+
+class Aap < Formula
+  desc "Make-like tool to download, build, and install software"
+  homepage "http://www.a-a-p.org"
+  url "https://downloads.sourceforge.net/project/a-a-p/aap-1.094.zip"
+  sha256 "3f53b2fc277756042449416150acc477f29de93692944f8a77e8cef285a1efd8"
+
+  bottle do
+    cellar :any_skip_relocation
+    rebuild 1
+    sha256 "533ff4c588ea7e7369570eb3ae7de191d9b62ed9161acbdeb0b07cb8d328aec7" => :mojave
+    sha256 "4fa43a4a0294b7e9040e13bf843e70fbeb189c2e505315dd574fbc9ed43bd060" => :high_sierra
+    sha256 "6e42400eb31e15dae56452b347925eb20faae1b996d94173b8711bd080e4a182" => :sierra
+    sha256 "32c30b38a37147754c5abfe9a801777b4a798af6dbcce2f15e1693c6027f0fbe" => :el_capitan
+    sha256 "15472b5a56a90d2d83c3ab24eba09e3644867857d8a7c547c82e6937beff3344" => :yosemite
+    sha256 "b141c07f091f90bd883148bf0e3c093d90fc0be7c4f8e7d07df9ae7cae684862" => :mavericks
+  end
+
+  depends_on "python@2" # does not support Python 3
+
+  def install
+    # Aap is designed to install using itself
+    system "./aap", "install", "PREFIX=#{prefix}", "MANSUBDIR=share/man"
+  end
+
+  test do
+    # A dummy target definition
+    (testpath/"main.aap").write("dummy:\n\t:print OK\n")
+    system "#{bin}/aap", "dummy"
+  end
+end
diff --git a/test/manager/homebrew/_fixtures/acmetool.rb b/test/manager/homebrew/_fixtures/acmetool.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c6d855447f06cc43e1ad620e6f65fbcd3e59ee01
--- /dev/null
+++ b/test/manager/homebrew/_fixtures/acmetool.rb
@@ -0,0 +1,195 @@
+require "language/go"
+
+=begin
+  url "https://github.com/hlandau/acme.git",
+=end
+# url "https://github.com/hlandau/acme.git",
+
+class Acmetool < Formula
+  desc "Automatic certificate acquisition tool for ACME (Let's Encrypt)"
+  homepage "https://github.com/hlandau/acme"
+  url "https://github.com/hlandau/acme.git",
+      :tag      => "v0.0.67",
+      :revision => "221ea15246f0bbcf254b350bee272d43a1820285"
+
+  bottle do
+    sha256 "6f2cf5cfb987a2df2f791c162209039804fd8fd12692da69f52153ec9668e9ca" => :mojave
+    sha256 "c4ff2b08c70560072307d64272f105bcd66c05983efbf1e278de9e5012047738" => :high_sierra
+    sha256 "7c77a51f12ec154cd5a82f066d547c70f8970a4c5046adf2ab99c600c930a9d5" => :sierra
+    sha256 "8f9a190bbda5a5cd209cf7f45bcdfff9c504a7e368458b435c78b1c25c8cb54b" => :el_capitan
+  end
+
+  depends_on "go" => :build
+
+  go_resource "github.com/alecthomas/template" do
+    url "https://github.com/alecthomas/template.git",
+        :revision => "a0175ee3bccc567396460bf5acd36800cb10c49c"
+  end
+
+  go_resource "github.com/alecthomas/units" do
+    url "https://github.com/alecthomas/units.git",
+        :revision => "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
+  end
+
+  go_resource "github.com/coreos/go-systemd" do
+    url "https://github.com/coreos/go-systemd.git",
+        :revision => "cc4f39464dc797b91c8025330de585294c2a6950"
+  end
+
+  go_resource "github.com/hlandau/buildinfo" do
+    url "https://github.com/hlandau/buildinfo.git",
+        :revision => "337a29b5499734e584d4630ce535af64c5fe7813"
+  end
+
+  go_resource "github.com/hlandau/dexlogconfig" do
+    url "https://github.com/hlandau/dexlogconfig.git",
+        :revision => "244f29bd260884993b176cd14ef2f7631f6f3c18"
+  end
+
+  go_resource "github.com/hlandau/goutils" do
+    url "https://github.com/hlandau/goutils.git",
+        :revision => "0cdb66aea5b843822af6fdffc21286b8fe8379c4"
+  end
+
+  go_resource "github.com/hlandau/xlog" do
+    url "https://github.com/hlandau/xlog.git",
+        :revision => "197ef798aed28e08ed3e176e678fda81be993a31"
+  end
+
+  go_resource "github.com/jmhodges/clock" do
+    url "https://github.com/jmhodges/clock.git",
+        :revision => "880ee4c335489bc78d01e4d0a254ae880734bc15"
+  end
+
+  go_resource "github.com/mattn/go-isatty" do
+    url "https://github.com/mattn/go-isatty.git",
+        :revision => "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c"
+  end
+
+  go_resource "github.com/mattn/go-runewidth" do
+    url "https://github.com/mattn/go-runewidth.git",
+        :revision => "97311d9f7767e3d6f422ea06661bc2c7a19e8a5d"
+  end
+
+  go_resource "github.com/mitchellh/go-wordwrap" do
+    url "https://github.com/mitchellh/go-wordwrap.git",
+        :revision => "ad45545899c7b13c020ea92b2072220eefad42b8"
+  end
+
+  go_resource "github.com/ogier/pflag" do
+    url "https://github.com/ogier/pflag.git",
+        :revision => "45c278ab3607870051a2ea9040bb85fcb8557481"
+  end
+
+  go_resource "github.com/peterhellberg/link" do
+    url "https://github.com/peterhellberg/link.git",
+        :revision => "8768c6d4dc563b4a09f58ecda04997024452c057"
+  end
+
+  go_resource "github.com/satori/go.uuid" do
+    url "https://github.com/satori/go.uuid.git",
+        :revision => "36e9d2ebbde5e3f13ab2e25625fd453271d6522e"
+  end
+
+  go_resource "github.com/shiena/ansicolor" do
+    url "https://github.com/shiena/ansicolor.git",
+        :revision => "a422bbe96644373c5753384a59d678f7d261ff10"
+  end
+
+  go_resource "golang.org/x/crypto" do
+    url "https://go.googlesource.com/crypto.git",
+        :revision => "a6600008915114d9c087fad9f03d75087b1a74df"
+  end
+
+  go_resource "golang.org/x/net" do
+    url "https://go.googlesource.com/net.git",
+        :revision => "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
+  end
+
+  go_resource "golang.org/x/sys" do
+    url "https://go.googlesource.com/sys.git",
+        :revision => "2c42eef0765b9837fbdab12011af7830f55f88f0"
+  end
+
+  go_resource "golang.org/x/text" do
+    url "https://go.googlesource.com/text.git",
+        :revision => "e19ae1496984b1c655b8044a65c0300a3c878dd3"
+  end
+
+  go_resource "gopkg.in/alecthomas/kingpin.v2" do
+    url "https://gopkg.in/alecthomas/kingpin.v2.git",
+        :revision => "947dcec5ba9c011838740e680966fd7087a71d0d"
+  end
+
+  go_resource "gopkg.in/cheggaaa/pb.v1" do
+    url "https://gopkg.in/cheggaaa/pb.v1.git",
+        :revision => "43d64de27312b32812ca7e994fa0bb03ccf08fdf"
+  end
+
+  go_resource "gopkg.in/hlandau/configurable.v1" do
+    url "https://gopkg.in/hlandau/configurable.v1.git",
+        :revision => "41496864a1fe3e0fef2973f22372b755d2897402"
+  end
+
+  go_resource "gopkg.in/hlandau/easyconfig.v1" do
+    url "https://gopkg.in/hlandau/easyconfig.v1.git",
+        :revision => "7589cb96edce2f94f8c1e6eb261f8c2b06220fe7"
+  end
+
+  go_resource "gopkg.in/hlandau/service.v2" do
+    url "https://gopkg.in/hlandau/service.v2.git",
+        :revision => "b64b3467ebd16f64faec1640c25e318efc0c0d7b"
+  end
+
+  go_resource "gopkg.in/hlandau/svcutils.v1" do
+    url "https://gopkg.in/hlandau/svcutils.v1.git",
+        :revision => "c25dac49e50cbbcbef8c81b089f56156f4067729"
+  end
+
+  go_resource "gopkg.in/square/go-jose.v1" do
+    url "https://gopkg.in/square/go-jose.v1.git",
+        :revision => "aa2e30fdd1fe9dd3394119af66451ae790d50e0d"
+  end
+
+  go_resource "gopkg.in/tylerb/graceful.v1" do
+    url "https://gopkg.in/tylerb/graceful.v1.git",
+        :revision => "4654dfbb6ad53cb5e27f37d99b02e16c1872fbbb"
+  end
+
+  go_resource "gopkg.in/yaml.v2" do
+    url "https://gopkg.in/yaml.v2.git",
+        :revision => "d670f9405373e636a5a2765eea47fac0c9bc91a4"
+  end
+
+  def install
+    ENV["GOPATH"] = buildpath
+
+    (buildpath/"src/github.com/hlandau").mkpath
+    ln_sf buildpath, buildpath/"src/github.com/hlandau/acme"
+    Language::Go.stage_deps resources, buildpath/"src"
+
+    cd "cmd/acmetool" do
+      # https://github.com/hlandau/acme/blob/master/_doc/PACKAGING-PATHS.md
+      ldflags = %W[
+        -X github.com/hlandau/acme/storage.RecommendedPath=#{var}/lib/acmetool
+        -X github.com/hlandau/acme/hooks.DefaultPath=#{lib}/hooks
+        -X github.com/hlandau/acme/responder.StandardWebrootPath=#{var}/run/acmetool/acme-challenge
+        #{Utils.popen_read("#{buildpath}/src/github.com/hlandau/buildinfo/gen")}
+      ]
+      system "go", "build", "-o", bin/"acmetool", "-ldflags", ldflags.join(" ")
+    end
+
+    (man8/"acmetool.8").write Utils.popen_read(bin/"acmetool", "--help-man")
+
+    doc.install Dir["_doc/*"]
+  end
+
+  def post_install
+    (var/"lib/acmetool").mkpath
+    (var/"run/acmetool").mkpath
+  end
+
+  test do
+    assert_match version.to_s, shell_output("#{bin}/acmetool --version", 2)
+  end
+end
diff --git a/test/manager/homebrew/_fixtures/aide.rb b/test/manager/homebrew/_fixtures/aide.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5e910779af3ac7b7e049eb4388a332e019dd513a
--- /dev/null
+++ b/test/manager/homebrew/_fixtures/aide.rb
@@ -0,0 +1,59 @@
+=begin
+  url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz"
+  sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7"
+=end
+# url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz"
+# sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7"
+
+$sha256 = "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7"
+class Aide < Formula
+  desc "File and directory integrity checker"
+  homepage "https://aide.github.io/"
+  url "https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz"
+  sha256 "0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7"
+
+  bottle do
+    cellar :any
+    sha256 "53b1dfabc76d6e54db56ec24f7f91b6cc9dcdd18210d17d2df92f86225fb9c9f" => :mojave
+    sha256 "79a2d4ce92526516891c844a4852161d39421f9dc31d2eba5ea0e48d79496053" => :high_sierra
+    sha256 "b626fcf7e52a0ea66fbed58bdc00cb08484f7bce8e84e61edf6740fbad7fabc5" => :sierra
+  end
+
+  head do
+    url "https://github.com/aide/aide.git"
+    depends_on "autoconf" => :build
+    depends_on "automake" => :build
+  end
+
+  depends_on "libgcrypt"
+  depends_on "libgpg-error"
+  depends_on "pcre"
+
+  def install
+    system "sh", "./autogen.sh" if build.head?
+
+    system "./configure", "--disable-lfs",
+                          "--disable-static",
+                          "--with-curl",
+                          "--with-zlib",
+                          "--sysconfdir=#{etc}",
+                          "--prefix=#{prefix}"
+
+    system "make", "install"
+  end
+
+  test do
+    (testpath/"aide.conf").write <<~EOS
+      database = file:/var/lib/aide/aide.db
+      database_out = file:/var/lib/aide/aide.db.new
+      database_new = file:/var/lib/aide/aide.db.new
+      gzip_dbout = yes
+      summarize_changes = yes
+      grouped = yes
+      verbose = 7
+      database_attrs = sha256
+      /etc p+i+u+g+sha256
+    EOS
+    system "#{bin}/aide", "--config-check", "-c", "aide.conf"
+  end
+end
diff --git a/test/manager/homebrew/_fixtures/ibazel.rb b/test/manager/homebrew/_fixtures/ibazel.rb
new file mode 100644
index 0000000000000000000000000000000000000000..edac288dbf4d1b258a4caf4adca313ce80a0ffee
--- /dev/null
+++ b/test/manager/homebrew/_fixtures/ibazel.rb
@@ -0,0 +1,47 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+=begin
+  url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+  sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+=end
+# url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+# sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+
+$sha256 = '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4';
+class Ibazel < Formula
+  desc 'IBazel is a tool for building Bazel targets when source files change.'
+  homepage 'https://github.com/bazelbuild/bazel-watcher'
+  url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+
+  # To generate run:
+  # curl https://codeload.github.com/bazelbuild/bazel-watcher/tar.gz/v0.8.2 | sha256sum
+  sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+
+  bottle :unneeded
+
+  depends_on "bazelbuild/tap/bazel" => :build
+
+  def install
+    system 'bazel', 'build', '--config=release', '--verbose_failures', '--experimental_platforms=@io_bazel_rules_go//go/toolchain:darwin_amd64', '//ibazel:ibazel'
+    bin.install 'bazel-bin/ibazel/darwin_amd64_pure_stripped/ibazel' => 'ibazel'
+  end
+
+  test do
+    # Since ibazel loops in most cases the quickest check of valididty
+    # I can think of is to get the version output which happens when
+    # invoked without any arguments.
+    system bin / 'ibazel'
+  end
+end
diff --git a/test/manager/homebrew/extract.spec.js b/test/manager/homebrew/extract.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..f5bd5fd101c101234af58d8cafe93b464c28b80c
--- /dev/null
+++ b/test/manager/homebrew/extract.spec.js
@@ -0,0 +1,185 @@
+const fs = require('fs');
+const { extractPackageFile } = require('../../../lib/manager/homebrew/extract');
+
+const aalib = fs.readFileSync(
+  'test/manager/homebrew/_fixtures/aalib.rb',
+  'utf8'
+);
+const aap = fs.readFileSync('test/manager/homebrew/_fixtures/aap.rb', 'utf8');
+const acmetool = fs.readFileSync(
+  'test/manager/homebrew/_fixtures/acmetool.rb',
+  'utf8'
+);
+const aide = fs.readFileSync('test/manager/homebrew/_fixtures/aide.rb', 'utf8');
+const ibazel = fs.readFileSync(
+  'test/manager/homebrew/_fixtures/ibazel.rb',
+  'utf8'
+);
+
+describe('lib/manager/homebrew/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('skips sourceforge dependency', () => {
+      const res = extractPackageFile(aalib);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips sourceforge dependency', () => {
+      const res = extractPackageFile(aap);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips github dependency with wrong format', () => {
+      const res = extractPackageFile(acmetool);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('extracts "releases" github dependency', () => {
+      const res = extractPackageFile(aide);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBeUndefined();
+      expect(res).toMatchSnapshot();
+    });
+    it('extracts "archive" github dependency', () => {
+      const res = extractPackageFile(ibazel);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBeUndefined();
+      expect(res).toMatchSnapshot();
+    });
+    it('handles no space before class header', () => {
+      const content = `class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBeUndefined();
+      expect(res).toMatchSnapshot();
+    });
+    it('returns null for invalid class header', () => {
+      const content = `
+          class Ibazel !?# Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      expect(extractPackageFile(content)).toBeNull();
+    });
+    it('returns null for invalid class header', () => {
+      const content = `
+          class Ibazel < NotFormula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      expect(extractPackageFile(content)).toBeNull();
+    });
+    it('skips if there is no url field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          not_url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if invalid url field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.not_tar.not_gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if invalid url field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/vInvalid.version.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if invalid url field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url ??https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if invalid url field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "invalid_url"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('unsupported-url');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if there is no sha256 field', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          not_sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('invalid-sha256');
+      expect(res).toMatchSnapshot();
+    });
+    it('skips if sha256 field is invalid', () => {
+      const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b0229'
+          end
+      `;
+      const res = extractPackageFile(content);
+      expect(res).not.toBeNull();
+      expect(res.deps[0].skipReason).toBe('invalid-sha256');
+      expect(res).toMatchSnapshot();
+    });
+  });
+});
diff --git a/test/manager/homebrew/update.spec.js b/test/manager/homebrew/update.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a3b39894fea38de21d18a2dc13a41f437dd36ab
--- /dev/null
+++ b/test/manager/homebrew/update.spec.js
@@ -0,0 +1,237 @@
+const got = require('got');
+const fs = require('fs');
+const { updateDependency } = require('../../../lib/manager/homebrew/update');
+
+jest.mock('got');
+
+const aide = fs.readFileSync('test/manager/homebrew/_fixtures/aide.rb', 'utf8');
+const ibazel = fs.readFileSync(
+  'test/manager/homebrew/_fixtures/ibazel.rb',
+  'utf8'
+);
+
+describe('manager/homebrew/update', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+  });
+  it('updates "releases" github dependency', async () => {
+    const upgrade = {
+      currentValue: 'v0.16.1',
+      depName: 'Aide',
+      ownerName: 'aide',
+      repoName: 'aide',
+      sha256:
+        '0f2b7cecc70c1a27d35c06c98804fcdb9f326630de5d035afc447122186010b7',
+      url:
+        'https://github.com/aide/aide/releases/download/v0.16.1/aide-0.16.1.tar.gz',
+      newValue: 'v0.17.7',
+    };
+    got.mockReturnValueOnce({ body: 'some_content_1' });
+    const newContent = await updateDependency(aide, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).not.toBe(aide);
+    expect(newContent).toMatchSnapshot();
+  });
+  it('updates "archive" github dependency', async () => {
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockReturnValueOnce({ body: 'some_content_2' });
+    const newContent = await updateDependency(ibazel, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).not.toBe(ibazel);
+    expect(newContent).toMatchSnapshot();
+  });
+  it('returns unchanged content if got function throws errors', async () => {
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      throw new Error('Request failed');
+    });
+    const newContent = await updateDependency(ibazel, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(ibazel);
+  });
+  it('returns unchanged content if url field in upgrade object is invalid', async () => {
+    const content = ibazel;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'invalid_url',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      return { body: 'some_content' };
+    });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if repoName in upgrade object is invalid', async () => {
+    const content = ibazel;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'invalid/repo/name',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got
+      .mockImplementationOnce(() => {
+        throw Error('Request failed');
+      })
+      .mockImplementationOnce(() => {
+        return { body: 'some_content' };
+      });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if repoName in upgrade object is wrong', async () => {
+    const content = ibazel;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'wrong-version/archive/v10.2.3.tar.gz',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got
+      .mockImplementationOnce(() => {
+        throw Error('Request failed');
+      })
+      .mockImplementationOnce(() => {
+        return { body: 'some_content' };
+      });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if url field in Formula file is invalid', async () => {
+    const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url ???https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      return { body: 'some_content' };
+    });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if url field in Formula file is missing', async () => {
+    const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          sha256 '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      return { body: 'some_content' };
+    });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if sha256 field in Formula file is invalid', async () => {
+    const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          sha256 ???26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4'
+          end
+      `;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      return { body: 'some_content' };
+    });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+  it('returns unchanged content if sha256 field in Formula file is missing', async () => {
+    const content = `
+          class Ibazel < Formula
+          desc 'IBazel is a tool for building Bazel targets when source files change.'
+          homepage 'https://github.com/bazelbuild/bazel-watcher'
+          url "https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz"
+          end
+      `;
+    const upgrade = {
+      currentValue: 'v0.8.2',
+      depName: 'Ibazel',
+      ownerName: 'bazelbuild',
+      repoName: 'bazel-watcher',
+      sha256:
+        '26f5125218fad2741d3caf937b02296d803900e5f153f5b1f733f15391b9f9b4',
+      url: 'https://github.com/bazelbuild/bazel-watcher/archive/v0.8.2.tar.gz',
+      newValue: 'v0.9.3',
+    };
+    got.mockImplementationOnce(() => {
+      return { body: 'some_content' };
+    });
+    const newContent = await updateDependency(content, upgrade);
+    expect(newContent).not.toBeNull();
+    expect(newContent).toBe(content);
+  });
+});
diff --git a/test/manager/homebrew/util.spec.js b/test/manager/homebrew/util.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..4f4fea0b7fb1471c0e5d49222cb0ed96ce8e5dab
--- /dev/null
+++ b/test/manager/homebrew/util.spec.js
@@ -0,0 +1,11 @@
+const { skip } = require('../../../lib/manager/homebrew/util');
+
+describe('lib/manager/homebrew/util', () => {
+  describe('skip()', () => {
+    it('handles out of bounds case', () => {
+      const content = 'some content';
+      const idx = content.length * 2;
+      expect(skip(idx, content, c => c === '!')).toBe(idx);
+    });
+  });
+});
diff --git a/test/workers/repository/extract/__snapshots__/index.spec.js.snap b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
index 31348bd03aa5ca48a8da802c6279438d195e5761..c801d4004405dae5cac8cfd72dfaebd62f241e59 100644
--- a/test/workers/repository/extract/__snapshots__/index.spec.js.snap
+++ b/test/workers/repository/extract/__snapshots__/index.spec.js.snap
@@ -44,6 +44,9 @@ Object {
   "gradle-wrapper": Array [
     Object {},
   ],
+  "homebrew": Array [
+    Object {},
+  ],
   "kubernetes": Array [
     Object {},
   ],
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index d6aa77408b4f4197c50f27cecfa78b8d318c9c5a..dc00ef3cf6ecf90687bf412628f5cd12412c9410 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -303,6 +303,8 @@ By default, Renovate will "slugify" the groupName to determine the branch name.
 
 And then the branchName would be `renovate/eslint` instead.
 
+## homebrew
+
 ## hostRules
 
 Example for configuring `docker` auth: