From 29251352e86ea785d68eed4baf71737a25830d7e Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Fri, 21 Sep 2018 05:43:51 +0200
Subject: [PATCH] feat: common prBody across all types (#2542)

Closes #442
---
 lib/config/definitions.js                     |  30 ---
 lib/config/migration.js                       |   2 +-
 lib/config/templates/default/pr-body.hbs      |  62 ------
 lib/config/templates/digest/pr-body.hbs       |  33 ---
 .../templates/docker-digest-group/pr-body.hbs |  37 ----
 .../templates/docker-digest/pr-body.hbs       |  31 ---
 lib/config/templates/docker-group/pr-body.hbs |  35 ---
 .../templates/docker-pin-group/pr-body.hbs    |  37 ----
 lib/config/templates/docker-pin/pr-body.hbs   |  34 ---
 lib/config/templates/docker/pr-body.hbs       |  31 ---
 lib/config/templates/group/pr-body.hbs        |  65 ------
 .../lock-file-maintenance/pr-body.hbs         |  31 ---
 lib/config/templates/node/pr-body.hbs         |  31 ---
 lib/config/validation.js                      |   1 +
 lib/workers/pr/changelog/hbs-template.js      |  32 +++
 lib/workers/pr/index.js                       |  51 +----
 lib/workers/pr/pr-body.js                     | 188 +++++++++++++++++
 test/config/__snapshots__/index.spec.js.snap  |   1 -
 .../__snapshots__/migration.spec.js.snap      |   2 +-
 test/config/migration.spec.js                 |   2 +-
 test/config/validation.spec.js                |   1 -
 .../pr/__snapshots__/index.spec.js.snap       | 199 ++++++++++++++++--
 test/workers/pr/index.spec.js                 |  83 +++++---
 .../__snapshots__/flatten.spec.js.snap        |  16 --
 website/docs/configuration-options.md         |   4 -
 25 files changed, 463 insertions(+), 576 deletions(-)
 delete mode 100644 lib/config/templates/default/pr-body.hbs
 delete mode 100644 lib/config/templates/digest/pr-body.hbs
 delete mode 100644 lib/config/templates/docker-digest-group/pr-body.hbs
 delete mode 100644 lib/config/templates/docker-digest/pr-body.hbs
 delete mode 100644 lib/config/templates/docker-group/pr-body.hbs
 delete mode 100644 lib/config/templates/docker-pin-group/pr-body.hbs
 delete mode 100644 lib/config/templates/docker-pin/pr-body.hbs
 delete mode 100644 lib/config/templates/docker/pr-body.hbs
 delete mode 100644 lib/config/templates/group/pr-body.hbs
 delete mode 100644 lib/config/templates/lock-file-maintenance/pr-body.hbs
 delete mode 100644 lib/config/templates/node/pr-body.hbs
 create mode 100644 lib/workers/pr/changelog/hbs-template.js
 create mode 100644 lib/workers/pr/pr-body.js

diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 7ff890f0af..77316711ee 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -1,17 +1,3 @@
-const fs = require('fs');
-const path = require('path');
-
-function template(name, subdir = 'default/') {
-  const shortName = `${name.replace(/([A-Z])/g, '-$1').toLowerCase()}.hbs`;
-  const hbsContents = fs.readFileSync(
-    // Long path is so that it works whether code is run from lib or dist
-    path.resolve(__dirname, '../config/templates/', subdir, shortName),
-    'utf8'
-  );
-  // Strip off any trailing line break
-  return hbsContents.replace(/\r?\n$/, '');
-}
-
 module.exports = {
   getOptions,
 };
@@ -613,7 +599,6 @@ const options = [
       branchTopic: '{{{depNameSanitized}}}-digest',
       commitMessageExtra: 'to {{newDigestShort}}',
       commitMessageTopic: '{{{depName}}} commit hash',
-      prBody: template('prBody', 'digest'),
     },
     cli: false,
     mergeable: true,
@@ -830,13 +815,6 @@ const options = [
     default: null,
     cli: false,
   },
-  {
-    name: 'prBody',
-    description: 'Pull Request body template',
-    type: 'string',
-    default: template('prBody'),
-    cli: false,
-  },
   {
     name: 'prFooter',
     description: 'Pull Request footer template',
@@ -859,7 +837,6 @@ const options = [
       commitMessageAction: 'Lock file maintenance',
       commitMessageTopic: null,
       commitMessageExtra: null,
-      prBody: template('prBody', 'lock-file-maintenance'),
       schedule: ['before 5am on monday'],
       groupName: null,
     },
@@ -895,7 +872,6 @@ const options = [
     default: {
       branchTopic: '{{{groupSlug}}}',
       commitMessageTopic: '{{{groupName}}}',
-      prBody: template('prBody', 'group'),
     },
     cli: false,
     env: false,
@@ -1034,33 +1010,27 @@ const options = [
     default: {
       managerBranchPrefix: 'docker-',
       commitMessageTopic: '{{{depName}}} Docker tag',
-      prBody: template('prBody', 'docker'),
       major: { enabled: false },
       rollbackPrs: false,
       digest: {
         branchTopic: '{{{depNameSanitized}}}-{{{currentValue}}}',
         commitMessageExtra: 'to {{newDigestShort}}',
-        prBody: template('prBody', 'docker-digest'),
         commitMessageTopic:
           '{{{depName}}}{{#if currentValue}}:{{{currentValue}}}{{/if}} Docker digest',
         group: {
-          prBody: template('prBody', 'docker-digest-group'),
           commitMessageTopic: '{{{groupName}}}',
         },
       },
       pin: {
         commitMessageExtra: '',
-        prBody: template('prBody', 'docker-pin'),
         groupName: 'Docker digests',
         group: {
-          prBody: template('prBody', 'docker-pin-group'),
           commitMessageTopic: '{{{groupName}}}',
           branchTopic: 'digests-pin',
         },
       },
       group: {
         commitMessageTopic: '{{{groupName}}} Docker tags',
-        prBody: template('prBody', 'docker-group'),
       },
     },
     mergeable: true,
diff --git a/lib/config/migration.js b/lib/config/migration.js
index 311cc47188..de3df564e7 100644
--- a/lib/config/migration.js
+++ b/lib/config/migration.js
@@ -364,7 +364,7 @@ function migrateConfig(config) {
           migratedConfig[key] = subMigrate.migratedConfig;
         }
       } else if (
-        (key === 'prBody' || key.startsWith('commitMessage')) &&
+        key.startsWith('commitMessage') &&
         val &&
         (val.includes('currentVersion') || val.includes('newVersion'))
       ) {
diff --git a/lib/config/templates/default/pr-body.hbs b/lib/config/templates/default/pr-body.hbs
deleted file mode 100644
index b527fcc4b3..0000000000
--- a/lib/config/templates/default/pr-body.hbs
+++ /dev/null
@@ -1,62 +0,0 @@
-This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} `{{{depName}}}` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from `{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}` to `{{#unless isRange}}v{{/unless}}{{{newValue}}}`{{#if isRollback}}. This is necessary and important because `v{{{currentValue}}}` cannot currently be found in the package's registry.{{/if}}
-{{#if hasTypes}}
-
-This PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.
-{{/if}}
-{{#if releases.length}}
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if isPin}}
-**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.
-{{/if}}
-{{#if hasReleaseNotes}}
-
-<details>
-<summary>Release Notes</summary>
-
-{{#each releases as |release|}}
-{{#if release.releaseNotes}}
-### [{{#if release.releaseNotes.name}}{{{release.releaseNotes.name}}}{{else}}`v{{{release.version}}}`{{/if}}]({{{release.releaseNotes.url}}})
-{{#if release.compare.url}}
-[Compare Source]({{release.compare.url}})
-
-{{/if}}
-{{{release.releaseNotes.body}}}
-
----
-
-{{/if}}
-{{/each}}
-</details>
-{{/if}}
-
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/digest/pr-body.hbs b/lib/config/templates/digest/pr-body.hbs
deleted file mode 100644
index ad4c852b7d..0000000000
--- a/lib/config/templates/digest/pr-body.hbs
+++ /dev/null
@@ -1,33 +0,0 @@
-This Pull Request updates dependency `{{{depName}}}` to the latest sha (`{{{newDigest}}}`).
-
-If you wish to disable digest updates, add `":disableDigestUpdates"` to the `extends` array in your config.
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker-digest-group/pr-body.hbs b/lib/config/templates/docker-digest-group/pr-body.hbs
deleted file mode 100644
index 999179dc15..0000000000
--- a/lib/config/templates/docker-digest-group/pr-body.hbs
+++ /dev/null
@@ -1,37 +0,0 @@
-This Pull Request updates Dockerfiles to the latest image digests. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#each upgrades as |upgrade|}}
--   {{#if repositoryUrl}}[{{upgrade.depName}}]({{upgrade.repositoryUrl}}){{else}}`{{{depName}}}`{{/if}}: `{{upgrade.newDigest}}`
-{{/each}}
-
-**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker-digest/pr-body.hbs b/lib/config/templates/docker-digest/pr-body.hbs
deleted file mode 100644
index 9f74c26803..0000000000
--- a/lib/config/templates/docker-digest/pr-body.hbs
+++ /dev/null
@@ -1,31 +0,0 @@
-This Pull Request updates Docker base image `{{{depName}}}:{{{currentTag}}}` to the latest digest (`{{{newDigest}}}`). For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker-group/pr-body.hbs b/lib/config/templates/docker-group/pr-body.hbs
deleted file mode 100644
index eb6d711b09..0000000000
--- a/lib/config/templates/docker-group/pr-body.hbs
+++ /dev/null
@@ -1,35 +0,0 @@
-This Pull Request renovates the Docker image group "{{{groupName}}}".
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#each upgrades as |upgrade|}}
--   {{#if repositoryUrl}}[{{{upgrade.depName}}}]({{upgrade.repositoryUrl}}){{else}}`{{{depName}}}`{{/if}}{{#if depType}} (`{{{depType}}}`){{/if}}: from `{{{upgrade.currentTag}}}` to `{{{upgrade.newValue}}}{{#if tagSuffix}}-{{{upgrade.tagSuffix}}}{{/if}}{{#if newDigest}}:{{{upgrade.newDigest}}}{{/if}}`
-{{/each}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker-pin-group/pr-body.hbs b/lib/config/templates/docker-pin-group/pr-body.hbs
deleted file mode 100644
index ec45b0beee..0000000000
--- a/lib/config/templates/docker-pin-group/pr-body.hbs
+++ /dev/null
@@ -1,37 +0,0 @@
-This Pull Request pins Dockerfiles to use image digests. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#each upgrades as |upgrade|}}
--   {{#if repositoryUrl}}[{{upgrade.depName}}]({{upgrade.repositoryUrl}}){{else}}`{{{depName}}}`{{/if}}: `{{upgrade.newDigest}}`
-{{/each}}
-
-**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker-pin/pr-body.hbs b/lib/config/templates/docker-pin/pr-body.hbs
deleted file mode 100644
index ca0ac21dd2..0000000000
--- a/lib/config/templates/docker-pin/pr-body.hbs
+++ /dev/null
@@ -1,34 +0,0 @@
-This Pull Request pins Docker base image `{{{depName}}}:{{{currentTag}}}` to use a digest (`{{{newDigest}}}`).
-This digest will then be kept updated via Pull Requests whenever the image is updated on the Docker registry. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/docker/pr-body.hbs b/lib/config/templates/docker/pr-body.hbs
deleted file mode 100644
index 2e1984dd13..0000000000
--- a/lib/config/templates/docker/pr-body.hbs
+++ /dev/null
@@ -1,31 +0,0 @@
-This Pull Request updates Docker base image `{{{depName}}}` from tag `{{{currentTag}}}` to new tag `{{{newValue}}}{{#if tagSuffix}}-{{{tagSuffix}}}{{/if}}`. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/group/pr-body.hbs b/lib/config/templates/group/pr-body.hbs
deleted file mode 100644
index a67ba41fe7..0000000000
--- a/lib/config/templates/group/pr-body.hbs
+++ /dev/null
@@ -1,65 +0,0 @@
-This Pull Request renovates the package group "{{{groupName}}}" with the following modifications:
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#each upgrades as |upgrade|}}
--   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} `{{{depName}}}` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from `{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}` to `{{#unless isRange}}v{{/unless}}{{{newValue}}}`
-{{/each}}
-
-{{#if hasReleaseNotes}}
-# Release Notes
-{{#each upgrades as |upgrade|}}
-{{#if upgrade.hasReleaseNotes}}
-<details>
-<summary>{{upgrade.githubName}}</summary>
-
-{{#each upgrade.releases as |release|}}
-{{#if release.releaseNotes}}
-### [`v{{{release.version}}}`]({{{release.releaseNotes.url}}})
-{{#if release.compare.url}}
-[Compare Source]({{release.compare.url}})
-
-{{/if}}
-{{{release.releaseNotes.body}}}
-
----
-
-{{/if}}
-{{/each}}
-
-</details>
-{{/if}}
-{{/each}}
-{{/if}}
-
-{{#if isPin}}
-**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/lock-file-maintenance/pr-body.hbs b/lib/config/templates/lock-file-maintenance/pr-body.hbs
deleted file mode 100644
index 8f46bf3029..0000000000
--- a/lib/config/templates/lock-file-maintenance/pr-body.hbs
+++ /dev/null
@@ -1,31 +0,0 @@
-This Pull Request updates `package.json` lock files to use the latest dependency versions.
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/templates/node/pr-body.hbs b/lib/config/templates/node/pr-body.hbs
deleted file mode 100644
index 307b739e48..0000000000
--- a/lib/config/templates/node/pr-body.hbs
+++ /dev/null
@@ -1,31 +0,0 @@
-This Pull Request updates `{{{depName}}}` versions from `{{{currentValue}}}` to `{{{newValue}}}`. This is according to the configured node.js support policy "{{{supportPolicy}}}".
-
-{{#if schedule}}
-**Note**: This PR was created on a configured schedule ("{{{schedule}}}"{{#if timezone}} in timezone `{{{timezone}}}`{{/if}}) and will not receive updates outside those times.
-{{/if}}
-
-{{#if hasErrors}}
-
----
-
-# Errors
-
-Renovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.
-
-{{#each errors as |error|}}
--   `{{error.depName}}`: {{error.message}}
-{{/each}}
-{{/if}}
-
-{{#if hasWarnings}}
-
----
-
-# Warnings
-
-Please make sure the following warnings are safe to ignore:
-
-{{#each warnings as |warning|}}
--   `{{warning.depName}}`: {{warning.message}}
-{{/each}}
-{{/if}}
diff --git a/lib/config/validation.js b/lib/config/validation.js
index ad4f074a67..19362acc8e 100644
--- a/lib/config/validation.js
+++ b/lib/config/validation.js
@@ -43,6 +43,7 @@ async function validateConfig(config, isPreset, parentPath) {
       'repository',
       'vulnerabilityAlertsOnly',
       'copyLocalLibs', // deprecated - functinoality is now enabled by default
+      'prBody', // deprecated
     ];
     return ignoredNodes.includes(key);
   }
diff --git a/lib/workers/pr/changelog/hbs-template.js b/lib/workers/pr/changelog/hbs-template.js
new file mode 100644
index 0000000000..6f2aba58a3
--- /dev/null
+++ b/lib/workers/pr/changelog/hbs-template.js
@@ -0,0 +1,32 @@
+module.exports = `### Release Notes
+
+{{#each upgrades as |upgrade|}}
+
+{{#if upgrade.hasReleaseNotes}}
+
+<details>
+<summary>{{upgrade.githubName}}</summary>
+
+{{#each upgrade.releases as |release|}}
+
+{{#if release.releaseNotes}}
+
+### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})
+
+{{#if release.compare.url}}
+
+[Compare Source]({{release.compare.url}})
+
+{{/if}}
+
+{{{release.releaseNotes.body}}}
+
+{{/if}}
+
+{{/each}}
+
+</details>
+
+{{/if}}
+
+{{/each}}`;
diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js
index f9d7124b0d..931214cf70 100644
--- a/lib/workers/pr/index.js
+++ b/lib/workers/pr/index.js
@@ -1,11 +1,10 @@
-const handlebars = require('handlebars');
 const changelogHelper = require('./changelog');
+const { getPrBody } = require('./pr-body');
 
 module.exports = {
   ensurePr,
   checkAutoMerge,
 };
-
 // Ensures that PR exists with matching title/body
 async function ensurePr(prConfig) {
   const config = { ...prConfig };
@@ -111,9 +110,6 @@ async function ensurePr(prConfig) {
   }
 
   const processedUpgrades = [];
-
-  const issueRe = /([\s(])#(\d+)([)\s]?)/g;
-
   const commitRepos = [];
 
   // Get changelog and then generate template strings
@@ -159,12 +155,6 @@ async function ensurePr(prConfig) {
 
   // Update the config object
   Object.assign(config, upgrades[0]);
-  if (config.errors && config.errors.length) {
-    config.hasErrors = true;
-  }
-  if (config.warnings && config.warnings.length) {
-    config.hasWarnings = true;
-  }
   config.hasReleaseNotes = config.upgrades.some(upg => upg.hasReleaseNotes);
   config.hasCommits = config.upgrades.some(upg => upg.hasCommits);
 
@@ -181,47 +171,12 @@ async function ensurePr(prConfig) {
     }
   }
 
-  let prBody = handlebars.compile(config.prBody)(config);
-  // istanbul ignore if
-  if (!config.isGroup && config.prBanner) {
-    const compiledBanner = handlebars.compile(config.prBanner)(config);
-    prBody = compiledBanner + '\n\n' + prBody;
-  }
-  // istanbul ignore if
-  if (config.global) {
-    if (config.global.prBanner) {
-      prBody = config.global.prBanner + '\n\n' + prBody;
-    }
-    if (config.global.prFooter) {
-      prBody = prBody + '\n---\n\n' + config.global.prFooter;
-    }
-  }
-  prBody = prBody.trim();
-
-  // Clean up double v's
-  prBody = prBody.replace(/\bvv(\d)/g, 'v$1');
-
-  // Generic replacements/link-breakers
-
-  // Put a zero width space after every # followed by a digit
-  prBody = prBody.replace(/#(\d)/gi, '#&#8203;$1');
-  // Put a zero width space after every @ symbol to prevent unintended hyperlinking
-  prBody = prBody.replace(/@/g, '@&#8203;');
-  prBody = prBody.replace(/(`\[?@)&#8203;/g, '$1');
-  prBody = prBody.replace(/([a-z]@)&#8203;/gi, '$1');
-  prBody = prBody.replace(issueRe, '$1#&#8203;$2$3');
-  // convert escaped backticks back to `
-  const backTickRe = /&#x60;([^/]*?)&#x60;/g;
-  prBody = prBody.replace(backTickRe, '`$1`');
-  prBody = prBody.replace(/`#&#8203;(\d+)`/g, '`#$1`');
-
-  prBody = prBody.replace(/\n\n\n+/g, '\n\n');
-
-  prBody = platform.getPrBody(prBody);
+  const prBody = await getPrBody(config);
 
   try {
     if (existingPr) {
       logger.debug('Processing existing PR');
+      // istanbul ignore if
       if (config.automerge && (await getBranchStatus()) === 'failure') {
         logger.debug(`Setting assignees and reviewers as status checks failed`);
         await addAssigneesReviewers(config, existingPr);
diff --git a/lib/workers/pr/pr-body.js b/lib/workers/pr/pr-body.js
new file mode 100644
index 0000000000..a54f982321
--- /dev/null
+++ b/lib/workers/pr/pr-body.js
@@ -0,0 +1,188 @@
+const handlebars = require('handlebars');
+const releaseNotesHbs = require('./changelog/hbs-template');
+
+module.exports = {
+  getUpdateHeaders,
+  getPrBody,
+};
+
+function getUpdateHeaders(config) {
+  const updateHeaders = ['Package'];
+  if (config.upgrades.some(upgrade => upgrade.depType)) {
+    updateHeaders.push('Type');
+  }
+  updateHeaders.push('Update');
+  updateHeaders.push('New value');
+  if (
+    config.upgrades.some(
+      upgrade =>
+        upgrade.homepage || upgrade.repositoryUrl || upgrade.changelogUrl
+    )
+  ) {
+    updateHeaders.push('References');
+  }
+  return updateHeaders;
+}
+
+async function getPrBody(config) {
+  let prBody = '';
+  // istanbul ignore if
+  if (config.prBanner && !config.isGroup) {
+    prBody += handlebars.compile(config.prBanner)(config) + '\n\n';
+  }
+  prBody += '\n\nThis PR contains the following updates:\n\n';
+  const updateHeaders = getUpdateHeaders(config);
+  prBody += '| ' + updateHeaders.join(' | ') + ' |\n';
+  prBody += '|' + updateHeaders.map(() => '--|').join('') + '\n';
+  const seen = [];
+  for (const upgrade of config.upgrades) {
+    const {
+      depName,
+      depType,
+      updateType,
+      newValue,
+      newDigestShort,
+      homepage,
+      repositoryUrl,
+      changelogUrl,
+    } = upgrade;
+    const key = depName + depType + updateType + newValue;
+    if (seen.includes(key)) {
+      // don't have duplicate rows
+      continue; // eslint-disable-line no-continue
+    }
+    seen.push(key);
+    let references = [];
+    if (homepage) {
+      references.push(`[homepage](${homepage})`);
+    }
+    if (repositoryUrl) {
+      references.push(`[source](${repositoryUrl})`);
+    }
+    if (changelogUrl) {
+      references.push(`[changelog](${changelogUrl})`);
+    }
+    references = references.join(', ');
+    let value = '';
+    if (newDigestShort) {
+      if (updateType === 'pin') {
+        value = config.newDigestShort;
+      }
+      if (newValue) {
+        value = newValue + '@' + newDigestShort;
+      } else {
+        value = newDigestShort;
+      }
+    } else if (updateType !== 'lockFileMaintenance') {
+      value = newValue;
+    }
+    const name =
+      upgrade.updateType === 'lockFileMaintenance'
+        ? 'all'
+        : '`' + depName + '`';
+    // prettier-ignore
+    prBody += `| ${name} | ${updateHeaders.includes('Type') ? depType + ' |' : ''} ${updateType} | ${value} |`;
+    if (updateHeaders.includes('References')) {
+      prBody += references + ' |';
+    }
+    prBody += '\n';
+  }
+  prBody += '\n\n';
+
+  if (config.upgrades.some(upgrade => upgrade.gitRef)) {
+    prBody +=
+      ':abcd: If you wish to disable git hash updates, add `":disableDigestUpdates"` to the extends array in your config.\n\n';
+  }
+
+  if (config.updateType === 'lockFileMaintenance') {
+    prBody +=
+      ':wrench: This Pull Request updates `package.json` lock files to use the latest dependency versions.\n\n';
+  }
+
+  if (config.isPin) {
+    prBody +=
+      ":pushpin: **Important**: Renovate will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset `:preserveSemverRanges` your config if you instead don't wish to pin dependencies.\n\n";
+  }
+  if (config.hasReleaseNotes) {
+    prBody +=
+      '\n\n---\n\n' + handlebars.compile(releaseNotesHbs)(config) + '\n\n';
+  }
+  prBody += '\n\n---\n\n### Renovate configuration\n\n';
+  prBody += ':date: **Schedule**: ';
+  if (config.schedule && config.schedule.length) {
+    prBody += `PR created on schedule "${config.schedule}"`;
+    if (config.timezone) {
+      prBody += ` in timezone ${config.timezone}`;
+    } else {
+      prBody += ` (UTC)`;
+    }
+  } else {
+    prBody += 'No schedule defined.';
+  }
+  prBody += '\n\n';
+  prBody += ':vertical_traffic_light: **Automerge**: ';
+  if (config.automerge) {
+    const branchStatus = await platform.getBranchStatus(
+      config.branchName,
+      config.requiredStatusChecks
+    );
+    // istanbul ignore if
+    if (branchStatus === 'failed') {
+      prBody += 'Disabled due to failing status checks.';
+    } else {
+      prBody += 'Enabled.';
+    }
+  } else {
+    prBody +=
+      'Disabled by config. Please merge this manually once you are satisfied.';
+  }
+  prBody += '\n\n';
+  prBody += ':recycle: **Rebasing**: ';
+  if (config.rebaseStalePrs) {
+    prBody +=
+      'Renovate will keep this PR updated whenever it falls behind the base branch.';
+  } else {
+    prBody +=
+      'Renovate will regenerate this PR any time it develops a merge conflict.';
+  }
+  prBody += '\n\n';
+  if (config.recreateClosed) {
+    prBody += `:ghost: **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/config-help/issues) if that's undesired.\n\n`;
+  } else {
+    prBody += `:no_bell: **Ignore**: Close this PR and you won't be reminded about ${
+      config.upgrades.length === 1 ? 'this update' : 'these updates'
+    } again.\n\n`;
+  }
+  // istanbul ignore if
+  if (config.global) {
+    if (config.global.prBanner) {
+      prBody = config.global.prBanner + '\n\n' + prBody;
+    }
+    if (config.global.prFooter) {
+      prBody = prBody + '\n---\n\n' + config.global.prFooter;
+    }
+  }
+  prBody = prBody.trim();
+
+  // Clean up double v's
+  prBody = prBody.replace(/\bvv(\d)/g, 'v$1');
+
+  // Generic replacements/link-breakers
+
+  // Put a zero width space after every # followed by a digit
+  prBody = prBody.replace(/#(\d)/gi, '#&#8203;$1');
+  // Put a zero width space after every @ symbol to prevent unintended hyperlinking
+  prBody = prBody.replace(/@/g, '@&#8203;');
+  prBody = prBody.replace(/(`\[?@)&#8203;/g, '$1');
+  prBody = prBody.replace(/([a-z]@)&#8203;/gi, '$1');
+  prBody = prBody.replace(/([\s(])#(\d+)([)\s]?)/g, '$1#&#8203;$2$3');
+  // convert escaped backticks back to `
+  const backTickRe = /&#x60;([^/]*?)&#x60;/g;
+  prBody = prBody.replace(backTickRe, '`$1`');
+  prBody = prBody.replace(/`#&#8203;(\d+)`/g, '`#$1`');
+
+  prBody = prBody.replace(/\n\n\n+/g, '\n\n');
+
+  prBody = platform.getPrBody(prBody);
+  return prBody;
+}
diff --git a/test/config/__snapshots__/index.spec.js.snap b/test/config/__snapshots__/index.spec.js.snap
index b890318691..0d1537999b 100644
--- a/test/config/__snapshots__/index.spec.js.snap
+++ b/test/config/__snapshots__/index.spec.js.snap
@@ -8,7 +8,6 @@ Object {
   "commitMessageTopic": null,
   "enabled": false,
   "groupName": null,
-  "prBody": "This Pull Request updates \`package.json\` lock files to use the latest dependency versions.\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
   "rebaseStalePrs": true,
   "recreateClosed": true,
   "schedule": Array [
diff --git a/test/config/__snapshots__/migration.spec.js.snap b/test/config/__snapshots__/migration.spec.js.snap
index 815ba4b5f2..ae963250b6 100644
--- a/test/config/__snapshots__/migration.spec.js.snap
+++ b/test/config/__snapshots__/migration.spec.js.snap
@@ -15,6 +15,7 @@ Object {
     "next",
   ],
   "commitMessage": "{{#if semanticCommitType}}{{semanticCommitType}}{{#if semanticCommitScope}}({{semanticCommitScope}}){{/if}}: {{/if}}some commit message",
+  "commitMessageExtra": "{{currentValue}} something",
   "enabled": true,
   "extends": Array [
     "config:js-app",
@@ -116,7 +117,6 @@ Object {
   "patch": Object {
     "automerge": true,
   },
-  "prBody": "{{currentValue}} something",
   "prTitle": "{{#if semanticCommitType}}{{semanticCommitType}}{{#if semanticCommitScope}}({{semanticCommitScope}}){{/if}}: {{/if}}some pr title",
   "rangeStrategy": "bump",
   "schedule": "on the first day of the month",
diff --git a/test/config/migration.spec.js b/test/config/migration.spec.js
index 37fc8a2096..265092f30a 100644
--- a/test/config/migration.spec.js
+++ b/test/config/migration.spec.js
@@ -30,7 +30,7 @@ describe('config/migration', () => {
         commitMessage: '{{semanticPrefix}}some commit message',
         prTitle: '{{semanticPrefix}}some pr title',
         semanticPrefix: 'fix(deps): ',
-        prBody: '{{currentVersion}} something',
+        commitMessageExtra: '{{currentVersion}} something',
         pathRules: [
           {
             paths: ['examples/**'],
diff --git a/test/config/validation.spec.js b/test/config/validation.spec.js
index c40dffeb3a..4a09518f33 100644
--- a/test/config/validation.spec.js
+++ b/test/config/validation.spec.js
@@ -21,7 +21,6 @@ describe('config/validation', () => {
             excludePackagePatterns: ['(x+x+)+y'],
           },
         ],
-        prBody: 'some-body',
         lockFileMaintenance: {
           bar: 2,
         },
diff --git a/test/workers/pr/__snapshots__/index.spec.js.snap b/test/workers/pr/__snapshots__/index.spec.js.snap
index 9720d6cf82..a0318deec0 100644
--- a/test/workers/pr/__snapshots__/index.spec.js.snap
+++ b/test/workers/pr/__snapshots__/index.spec.js.snap
@@ -1,7 +1,5 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`workers/pr ensurePr should add assignees and reviewers to existing PR 1`] = `Array []`;
-
 exports[`workers/pr ensurePr should add assignees and reviewers to new PR 1`] = `
 Array [
   Array [
@@ -26,21 +24,135 @@ Array [
 ]
 `;
 
+exports[`workers/pr ensurePr should add note about Pin 1`] = `
+Array [
+  "renovate/dummy-1.x",
+  "Update dependency dummy to v1.1.0",
+  "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+
+:pushpin: **Important**: Renovate will wait until you have merged this Pin PR before creating any *upgrade* PRs for the affected packages. Add the preset \`:preserveSemverRanges\` your config if you instead don't wish to pin dependencies.
+
+---
+
+### Release Notes
+
+<details>
+<summary>renovateapp/dummy</summary>
+
+### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
+[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
+</details>
+
+---
+
+### Renovate configuration
+
+:date: **Schedule**: PR created on schedule \\"before 5am\\" in timezone some timezone
+
+:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
+
+:recycle: **Rebasing**: Renovate will keep this PR updated whenever it falls behind the base branch.
+
+:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.",
+  Array [],
+  false,
+  false,
+]
+`;
+
 exports[`workers/pr ensurePr should create PR if success 1`] = `
 Array [
   "renovate/dummy-1.x",
   "Update dependency dummy to v1.1.0",
-  "This Pull Request updates dependency \`dummy\` ([source](https://github.com/renovateapp/dummy)) from \`v1.0.0\` to \`v1.1.0\`
+  "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+
+---
+
+### Release Notes
 
 <details>
-<summary>Release Notes</summary>
+<summary>renovateapp/dummy</summary>
 
 ### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
 [Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
 
+</details>
+
 ---
 
-</details>",
+### Renovate configuration
+
+:date: **Schedule**: PR created on schedule \\"before 5am\\" (UTC)
+
+:vertical_traffic_light: **Automerge**: Enabled.
+
+:recycle: **Rebasing**: Renovate will regenerate this PR any time it develops a merge conflict.
+
+:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.",
+  Array [],
+  false,
+  false,
+]
+`;
+
+exports[`workers/pr ensurePr should create group PR 1`] = `
+Array [
+  "renovate/dummy-1.x",
+  "Update dependency dummy to v1.1.0",
+  "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| all | devDependencies | lockFileMaintenance |  |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+| \`a\` | undefined | undefined | aaaaaaa | |
+| \`b\` | undefined | pin | some_new_value@bbbbbbb | |
+| \`c\` | undefined | undefined | undefined | |
+| all | undefined | lockFileMaintenance |  | |
+
+:abcd: If you wish to disable git hash updates, add \`\\":disableDigestUpdates\\"\` to the extends array in your config.
+
+:wrench: This Pull Request updates \`package.json\` lock files to use the latest dependency versions.
+
+---
+
+### Release Notes
+
+<details>
+<summary>renovateapp/dummy</summary>
+
+### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
+[Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
+</details>
+
+<details>
+<summary>renovateapp/dummy</summary>
+
+</details>
+
+---
+
+### Renovate configuration
+
+:date: **Schedule**: No schedule defined.
+
+:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
+
+:recycle: **Rebasing**: Renovate will regenerate this PR any time it develops a merge conflict.
+
+:ghost: **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/config-help/issues) if that's undesired.",
   Array [],
   false,
   false,
@@ -51,17 +163,36 @@ exports[`workers/pr ensurePr should create privateRepo PR if success 1`] = `
 Array [
   "renovate/dummy-1.x",
   "Update dependency dummy to v1.1.0",
-  "This Pull Request updates dependency \`dummy\` ([source](https://github.com/renovateapp/dummy)) from \`v1.0.0\` to \`v1.1.0\`
+  "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+
+---
+
+### Release Notes
 
 <details>
-<summary>Release Notes</summary>
+<summary>renovateapp/dummy</summary>
 
 ### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
 [Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
 
+</details>
+
 ---
 
-</details>",
+### Renovate configuration
+
+:date: **Schedule**: No schedule defined.
+
+:vertical_traffic_light: **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
+
+:recycle: **Rebasing**: Renovate will regenerate this PR any time it develops a merge conflict.
+
+:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.",
   Array [],
   false,
   false,
@@ -70,17 +201,37 @@ Array [
 
 exports[`workers/pr ensurePr should return modified existing PR 1`] = `
 Object {
-  "body": "This Pull Request updates dependency \`dummy\` ([source](https://github.com/renovateapp/dummy)) from \`v1.0.0\` to \`v1.1.0\`
+  "body": "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+
+---
+
+### Release Notes
 
 <details>
-<summary>Release Notes</summary>
+<summary>renovateapp/dummy</summary>
 
 ### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
 [Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
 
+</details>
+
 ---
 
-</details>",
+### Renovate configuration
+
+:date: **Schedule**: PR created on schedule \\"before 5am\\" (UTC)
+
+:vertical_traffic_light: **Automerge**: Enabled.
+
+:recycle: **Rebasing**: Renovate will regenerate this PR any time it develops a merge conflict.
+
+:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.",
+  "canRebase": true,
   "displayNumber": "Existing PR",
   "title": "Update dependency dummy to v1.1.0",
 }
@@ -88,17 +239,37 @@ Object {
 
 exports[`workers/pr ensurePr should return modified existing PR title 1`] = `
 Object {
-  "body": "This Pull Request updates dependency \`dummy\` ([source](https://github.com/renovateapp/dummy)) from \`v1.0.0\` to \`v1.1.0\`
+  "body": "This PR contains the following updates:
+
+| Package | Type | Update | New value | References |
+|--|--|--|--|--|
+| \`dummy\` | devDependencies | undefined | 1.1.0 |[homepage](https://dummy.com), [source](https://github.com/renovateapp/dummy), [changelog](https://github.com/renovateapp/dummy/changelog.md) |
+
+---
+
+### Release Notes
 
 <details>
-<summary>Release Notes</summary>
+<summary>renovateapp/dummy</summary>
 
 ### [\`v1.1.0\`](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
+
 [Compare Source](https://github.com/renovateapp/dummy/compare/v1.0.0...v1.1.0)
 
+</details>
+
 ---
 
-</details>",
+### Renovate configuration
+
+:date: **Schedule**: PR created on schedule \\"before 5am\\" (UTC)
+
+:vertical_traffic_light: **Automerge**: Enabled.
+
+:recycle: **Rebasing**: Renovate will regenerate this PR any time it develops a merge conflict.
+
+:no_bell: **Ignore**: Close this PR and you won't be reminded about this update again.",
+  "canRebase": true,
   "displayNumber": "Existing PR",
   "title": "wrong",
 }
diff --git a/test/workers/pr/index.spec.js b/test/workers/pr/index.spec.js
index b30536cb27..1f42eba6fb 100644
--- a/test/workers/pr/index.spec.js
+++ b/test/workers/pr/index.spec.js
@@ -103,6 +103,7 @@ describe('workers/pr', () => {
       title: 'Update dependency dummy to v1.1.0',
       body:
         'Some body<!-- Reviewable:start -->something<!-- Reviewable:end -->\n\n',
+      canRebase: true,
     };
     beforeEach(() => {
       config = {
@@ -110,12 +111,15 @@ describe('workers/pr', () => {
       };
       config.branchName = 'renovate/dummy-1.x';
       config.prTitle = 'Update dependency dummy to v1.1.0';
+      config.depType = 'devDependencies';
       config.depName = 'dummy';
       config.isGitHub = true;
       config.privateRepo = true;
       config.currentValue = '1.0.0';
       config.newValue = '1.1.0';
+      config.homepage = 'https://dummy.com';
       config.repositoryUrl = 'https://github.com/renovateapp/dummy';
+      config.changelogUrl = 'https://github.com/renovateapp/dummy/changelog.md';
       platform.createPr.mockReturnValue({ displayNumber: 'New Pull Request' });
       config.upgrades = [config];
       platform.getPrBody = jest.fn(input => input);
@@ -141,11 +145,54 @@ describe('workers/pr', () => {
     it('should create PR if success', async () => {
       platform.getBranchStatus.mockReturnValueOnce('success');
       config.prCreation = 'status-success';
+      config.automerge = true;
+      config.schedule = 'before 5am';
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
       expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
       existingPr.body = platform.createPr.mock.calls[0][2];
     });
+    it('should create group PR', async () => {
+      config.upgrades = config.upgrades.concat([
+        {
+          depName: 'a',
+          newDigestShort: 'aaaaaaa',
+        },
+        {
+          depName: 'b',
+          newDigestShort: 'bbbbbbb',
+          newValue: 'some_new_value',
+          updateType: 'pin',
+        },
+        {
+          depName: 'c',
+          gitRef: 'ccccccc',
+        },
+        {
+          depName: 'd',
+          updateType: 'lockFileMaintenance',
+        },
+      ]);
+      config.updateType = 'lockFileMaintenance';
+      config.recreateClosed = true;
+      const pr = await prWorker.ensurePr(config);
+      expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
+      expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
+    });
+    it('should add note about Pin', async () => {
+      platform.getBranchStatus.mockReturnValueOnce('success');
+      config.prCreation = 'status-success';
+      config.isPin = true;
+      config.schedule = 'before 5am';
+      config.timezone = 'some timezone';
+      config.rebaseStalePrs = true;
+      const pr = await prWorker.ensurePr(config);
+      expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
+      expect(platform.createPr.mock.calls[0]).toMatchSnapshot();
+      expect(platform.createPr.mock.calls[0][2].includes('this Pin PR')).toBe(
+        true
+      );
+    });
     it('should return null if creating PR fails', async () => {
       platform.getBranchStatus.mockReturnValueOnce('success');
       platform.createPr = jest.fn();
@@ -181,12 +228,6 @@ describe('workers/pr', () => {
     it('should create new branch if none exists', async () => {
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
-      expect(platform.createPr.mock.calls[0][2].indexOf('Errors</h3>')).toEqual(
-        -1
-      );
-      expect(
-        platform.createPr.mock.calls[0][2].indexOf('Warnings</h3>')
-      ).toEqual(-1);
     });
     it('should add assignees and reviewers to new PR', async () => {
       config.assignees = ['@foo', 'bar'];
@@ -220,18 +261,6 @@ describe('workers/pr', () => {
       expect(platform.addAssignees.mock.calls.length).toBe(1);
       expect(platform.addReviewers.mock.calls.length).toBe(1);
     });
-    it('should display errors and warnings', async () => {
-      config.errors = [{}];
-      config.warnings = [{}];
-      const pr = await prWorker.ensurePr(config);
-      expect(
-        platform.createPr.mock.calls[0][2].indexOf('# Errors')
-      ).not.toEqual(-1);
-      expect(
-        platform.createPr.mock.calls[0][2].indexOf('# Warnings')
-      ).not.toEqual(-1);
-      expect(pr).toMatchObject({ displayNumber: 'New Pull Request' });
-    });
     it('should not add assignees and reviewers to new PR if automerging enabled', async () => {
       config.assignees = ['bar'];
       config.reviewers = ['baz'];
@@ -241,23 +270,11 @@ describe('workers/pr', () => {
       expect(platform.addAssignees.mock.calls.length).toBe(0);
       expect(platform.addReviewers.mock.calls.length).toBe(0);
     });
-    it('should add assignees and reviewers to existing PR', async () => {
-      config.assignees = ['bar'];
-      config.reviewers = ['baz'];
-      config.automerge = true;
-      platform.getBranchPr.mockReturnValueOnce(existingPr);
-      platform.getBranchStatus.mockReturnValueOnce('failure');
-      config.semanticCommitScope = null;
-      const pr = await prWorker.ensurePr(config);
-      expect(platform.updatePr.mock.calls).toMatchSnapshot();
-      expect(platform.updatePr.mock.calls.length).toBe(0);
-      expect(platform.addAssignees.mock.calls.length).toBe(1);
-      expect(platform.addReviewers.mock.calls.length).toBe(1);
-      expect(pr).toMatchObject(existingPr);
-    });
     it('should return unmodified existing PR', async () => {
       platform.getBranchPr.mockReturnValueOnce(existingPr);
       config.semanticCommitScope = null;
+      config.automerge = true;
+      config.schedule = 'before 5am';
       const pr = await prWorker.ensurePr(config);
       expect(platform.updatePr.mock.calls).toMatchSnapshot();
       expect(platform.updatePr.mock.calls).toHaveLength(0);
@@ -265,6 +282,8 @@ describe('workers/pr', () => {
     });
     it('should return modified existing PR', async () => {
       config.newValue = '1.2.0';
+      config.automerge = true;
+      config.schedule = 'before 5am';
       platform.getBranchPr.mockReturnValueOnce(existingPr);
       const pr = await prWorker.ensurePr(config);
       expect(pr).toMatchSnapshot();
diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
index 50debd4670..3aaf8a1f59 100644
--- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
+++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap
@@ -27,7 +27,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -41,7 +40,6 @@ Array [
     "npmrc": null,
     "packageFile": "package.json",
     "platform": "github",
-    "prBody": "This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`{{#if isRollback}}. This is necessary and important because \`v{{{currentValue}}}\` cannot currently be found in the package's registry.{{/if}}\\n{{#if hasTypes}}\\n\\nThis PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.\\n{{/if}}\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [{{#if release.releaseNotes.name}}{{{release.releaseNotes.name}}}{{else}}\`v{{{release.version}}}\`{{/if}}]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -100,7 +98,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -114,7 +111,6 @@ Array [
     "npmrc": null,
     "packageFile": "package.json",
     "platform": "github",
-    "prBody": "This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`{{#if isRollback}}. This is necessary and important because \`v{{{currentValue}}}\` cannot currently be found in the package's registry.{{/if}}\\n{{#if hasTypes}}\\n\\nThis PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.\\n{{/if}}\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [{{#if release.releaseNotes.name}}{{{release.releaseNotes.name}}}{{else}}\`v{{{release.version}}}\`{{/if}}]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -171,7 +167,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -184,7 +179,6 @@ Array [
     "npmrc": null,
     "packageFile": "package.json",
     "platform": "github",
-    "prBody": "This Pull Request updates \`package.json\` lock files to use the latest dependency versions.\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -246,7 +240,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -260,7 +253,6 @@ Array [
     "npmrc": null,
     "packageFile": "backend/package.json",
     "platform": "github",
-    "prBody": "This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`{{#if isRollback}}. This is necessary and important because \`v{{{currentValue}}}\` cannot currently be found in the package's registry.{{/if}}\\n{{#if hasTypes}}\\n\\nThis PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.\\n{{/if}}\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [{{#if release.releaseNotes.name}}{{{release.releaseNotes.name}}}{{else}}\`v{{{release.version}}}\`{{/if}}]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -317,7 +309,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -330,7 +321,6 @@ Array [
     "npmrc": null,
     "packageFile": "backend/package.json",
     "platform": "github",
-    "prBody": "This Pull Request updates \`package.json\` lock files to use the latest dependency versions.\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -392,7 +382,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}}",
-      "prBody": "This Pull Request renovates the package group \\"{{{groupName}}}\\" with the following modifications:\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if isPin}}Pins{{else}}Updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`\\n{{/each}}\\n\\n{{#if hasReleaseNotes}}\\n# Release Notes\\n{{#each upgrades as |upgrade|}}\\n{{#if upgrade.hasReleaseNotes}}\\n<details>\\n<summary>{{upgrade.githubName}}</summary>\\n\\n{{#each upgrade.releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [\`v{{{release.version}}}\`]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n\\n</details>\\n{{/if}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -406,7 +395,6 @@ Array [
     "npmrc": null,
     "packageFile": "frontend/package.json",
     "platform": "github",
-    "prBody": "This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} {{#if prettyDepType}}{{{prettyDepType}}}{{else}}dependency{{/if}} \`{{{depName}}}\` {{#if hasUrls}}({{#if homepage}}[homepage]({{{homepage}}}){{#if repositoryUrl}}, [source]({{{repositoryUrl}}}){{/if}}{{else}}[source]({{{repositoryUrl}}}){{/if}}{{#if changelogUrl}}, [changelog]({{{changelogUrl}}}){{/if}}){{/if}} from \`{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{{currentValue}}}\` to \`{{#unless isRange}}v{{/unless}}{{{newValue}}}\`{{#if isRollback}}. This is necessary and important because \`v{{{currentValue}}}\` cannot currently be found in the package's registry.{{/if}}\\n{{#if hasTypes}}\\n\\nThis PR also includes an upgrade to the corresponding [@types/{{{depName}}}](https://npmjs.com/package/@types/{{{depName}}}) package.\\n{{/if}}\\n{{#if releases.length}}\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if isPin}}\\n**Important**: Renovate will wait until you have merged this Pin request before creating PRs for any *upgrades*. If you do not wish to pin anything, please update your config accordingly instead of leaving this PR open.\\n{{/if}}\\n{{#if hasReleaseNotes}}\\n\\n<details>\\n<summary>Release Notes</summary>\\n\\n{{#each releases as |release|}}\\n{{#if release.releaseNotes}}\\n### [{{#if release.releaseNotes.name}}{{{release.releaseNotes.name}}}{{else}}\`v{{{release.version}}}\`{{/if}}]({{{release.releaseNotes.url}}})\\n{{#if release.compare.url}}\\n[Compare Source]({{release.compare.url}})\\n\\n{{/if}}\\n{{{release.releaseNotes.body}}}\\n\\n---\\n\\n{{/if}}\\n{{/each}}\\n</details>\\n{{/if}}\\n\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -465,7 +453,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}} Docker tags",
-      "prBody": "This Pull Request renovates the Docker image group \\"{{{groupName}}}\\".\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if repositoryUrl}}[{{{upgrade.depName}}}]({{upgrade.repositoryUrl}}){{else}}\`{{{depName}}}\`{{/if}}{{#if depType}} (\`{{{depType}}}\`){{/if}}: from \`{{{upgrade.currentTag}}}\` to \`{{{upgrade.newValue}}}{{#if tagSuffix}}-{{{upgrade.tagSuffix}}}{{/if}}{{#if newDigest}}:{{{upgrade.newDigest}}}{{/if}}\`\\n{{/each}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -479,7 +466,6 @@ Array [
     "npmrc": null,
     "packageFile": "Dockerfile",
     "platform": "github",
-    "prBody": "This Pull Request updates Docker base image \`{{{depName}}}\` from tag \`{{{currentTag}}}\` to new tag \`{{{newValue}}}{{#if tagSuffix}}-{{{tagSuffix}}}{{/if}}\`. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
@@ -538,7 +524,6 @@ Array [
     "group": Object {
       "branchTopic": "{{{groupSlug}}}",
       "commitMessageTopic": "{{{groupName}}} Docker tags",
-      "prBody": "This Pull Request renovates the Docker image group \\"{{{groupName}}}\\".\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#each upgrades as |upgrade|}}\\n-   {{#if repositoryUrl}}[{{{upgrade.depName}}}]({{upgrade.repositoryUrl}}){{else}}\`{{{depName}}}\`{{/if}}{{#if depType}} (\`{{{depType}}}\`){{/if}}: from \`{{{upgrade.currentTag}}}\` to \`{{{upgrade.newValue}}}{{#if tagSuffix}}-{{{upgrade.tagSuffix}}}{{/if}}{{#if newDigest}}:{{{upgrade.newDigest}}}{{/if}}\`\\n{{/each}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     },
     "groupName": null,
     "groupSlug": null,
@@ -552,7 +537,6 @@ Array [
     "npmrc": null,
     "packageFile": "Dockerfile",
     "platform": "github",
-    "prBody": "This Pull Request updates Docker base image \`{{{depName}}}\` from tag \`{{{currentTag}}}\` to new tag \`{{{newValue}}}{{#if tagSuffix}}-{{{tagSuffix}}}{{/if}}\`. For details on Renovate's Docker support, please visit https://renovatebot.com/docs/docker\\n\\n{{#if schedule}}\\n**Note**: This PR was created on a configured schedule (\\"{{{schedule}}}\\"{{#if timezone}} in timezone \`{{{timezone}}}\`{{/if}}) and will not receive updates outside those times.\\n{{/if}}\\n\\n{{#if hasErrors}}\\n\\n---\\n\\n# Errors\\n\\nRenovate encountered some errors when processing your repository, so you are being notified here even if they do not directly apply to this PR.\\n\\n{{#each errors as |error|}}\\n-   \`{{error.depName}}\`: {{error.message}}\\n{{/each}}\\n{{/if}}\\n\\n{{#if hasWarnings}}\\n\\n---\\n\\n# Warnings\\n\\nPlease make sure the following warnings are safe to ignore:\\n\\n{{#each warnings as |warning|}}\\n-   \`{{warning.depName}}\`: {{warning.message}}\\n{{/each}}\\n{{/if}}",
     "prConcurrentLimit": 0,
     "prCreation": "immediate",
     "prHourlyLimit": 0,
diff --git a/website/docs/configuration-options.md b/website/docs/configuration-options.md
index 11c405a769..fb08523b5c 100644
--- a/website/docs/configuration-options.md
+++ b/website/docs/configuration-options.md
@@ -543,10 +543,6 @@ By default, Renovate will add sha256 digests to Docker source images so that the
 
 Add configuration here to specifically override settings for `pip` requirements files. Supports `requirements.txt` and `requirements.pip` files. The default file pattern is fairly flexible in an attempt to catch similarly named ones too but may be extended/changed.
 
-## prBody
-
-Although the PR body can be customised by you, it might be quite challenging. If you think the Pull Request should include different information or could be formatted better, perhaps try raising an [Issue](https://github.com/renovatebot/renovate/issues) and let us solve it for you and for everyone else too.
-
 ## prConcurrentLimit
 
 This setting - if enabled - limits Renovate to a maximum of x concurrent PRs open at any time.
-- 
GitLab