From 70c95add5aaffd8ff84c56e65f7e541ca5951b33 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@keylocation.sg>
Date: Sat, 9 Dec 2017 17:09:31 +0100
Subject: [PATCH] feat: custom git commit author (#1280)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This PR adds the capability to specify a custom author for git commits on GitHub. Setting this field will mean GitHub uses this value for author and commit instead of the token’s identity. For instance if you are running hosted mode you may set the gitAuthor to “Renovate Bot <bot@renovate.com>” to have commits appear as coming from the renovate-bot account.
---
 docs/configuration.md                         | 20 ++++---
 lib/config/definitions.js                     |  5 ++
 lib/platform/github/index.js                  | 43 +++++++++++----
 lib/workers/branch/commit.js                  |  3 +-
 package.json                                  |  1 +
 .../__snapshots__/resolve.spec.js.snap        |  7 +++
 test/platform/github/index.spec.js            | 53 +++++++++++++++++++
 .../branch/__snapshots__/commit.spec.js.snap  |  2 +
 .../package/__snapshots__/index.spec.js.snap  |  1 +
 .../__snapshots__/branchify.spec.js.snap      |  5 ++
 yarn.lock                                     |  4 ++
 11 files changed, 126 insertions(+), 18 deletions(-)

diff --git a/docs/configuration.md b/docs/configuration.md
index e75a786e08..d05d255f08 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -309,6 +309,14 @@ location with this method.
   <td></td>
   <td><td>
 </tr>
+<tr>
+  <td>`gitAuthor`</td>
+  <td>Author to use for git commits. RFC5322</td>
+  <td>string</td>
+  <td><pre>null</pre></td>
+  <td>`RENOVATE_GIT_AUTHOR`</td>
+  <td>`--git-author`<td>
+</tr>
 <tr>
   <td>`packageFiles`</td>
   <td>Package file paths</td>
@@ -649,7 +657,7 @@ location with this method.
   <td>`prBody`</td>
   <td>Pull Request body template</td>
   <td>string</td>
-  <td><pre>"This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} dependency {{#if repositoryUrl}}[{{depName}}]({{repositoryUrl}}){{else}}`{{depName}}`{{/if}} from `{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{currentVersion}}` to `{{#unless isRange}}v{{/unless}}{{newVersion}}`{{#if isRollback}}. This is necessary and important because `v{{currentVersion}}` cannot be found in the npm registry - probably because of it being unpublished.{{/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### Commits\n\n<details>\n<summary>{{githubName}}</summary>\n\n{{#each releases as |release|}}\n#### {{release.version}}\n{{#each release.commits as |commit|}}\n-   [`{{commit.shortSha}}`]({{commit.url}}) {{commit.message}}\n{{/each}}\n{{/each}}\n\n</details>\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."</pre></td>
+  <td><pre>"This Pull Request {{#if isRollback}}rolls back{{else}}updates{{/if}} dependency {{#if repositoryUrl}}[{{depName}}]({{repositoryUrl}}){{else}}{{depName}}{{/if}} from `{{#unless isRange}}{{#unless isPin}}v{{/unless}}{{/unless}}{{currentVersion}}` to `{{#unless isRange}}v{{/unless}}{{newVersion}}`{{#if isRollback}}. This is necessary and important because `v{{currentVersion}}` cannot be found in the npm registry - probably because of it being unpublished.{{/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### Commits\n\n<details>\n<summary>{{githubName}}</summary>\n\n{{#each releases as |release|}}\n#### {{release.version}}\n{{#each release.commits as |commit|}}\n-   [`{{commit.shortSha}}`]({{commit.url}}) {{commit.message}}\n{{/each}}\n{{/each}}\n\n</details>\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."</pre></td>
   <td>`RENOVATE_PR_BODY`</td>
   <td><td>
 </tr>
@@ -703,7 +711,7 @@ location with this method.
   "branchName": "{{branchPrefix}}{{groupSlug}}",
   "commitMessage": "Renovate {{groupName}} packages",
   "prTitle": "Renovate {{groupName}} packages",
-  "prBody": "This Pull Request renovates the package 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}}: from `{{upgrade.currentVersion}}` to `{{upgrade.newVersion}}`\n{{/each}}\n\n{{#unless isPin}}\n### Commits\n\n{{#each upgrades as |upgrade|}}\n{{#if upgrade.releases.length}}\n<details>\n<summary>{{upgrade.githubName}}</summary>\n{{#each upgrade.releases as |release|}}\n\n#### {{release.version}}\n{{#each release.commits as |commit|}}\n-   [`{{commit.shortSha}}`]({{commit.url}}){{commit.message}}\n{{/each}}\n{{/each}}\n\n</details>\n{{/if}}\n{{/each}}\n{{/unless}}\n<br />\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
+  "prBody": "This Pull Request renovates the package 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}}: from `{{upgrade.currentVersion}}` to `{{upgrade.newVersion}}`\n{{/each}}\n\n{{#unless isPin}}\n### Commits\n\n{{#each upgrades as |upgrade|}}\n{{#if upgrade.releases.length}}\n<details>\n<summary>{{upgrade.githubName}}</summary>\n{{#each upgrade.releases as |release|}}\n\n#### {{release.version}}\n{{#each release.commits as |commit|}}\n-   [`{{commit.shortSha}}`]({{commit.url}}){{commit.message}}\n{{/each}}\n{{/each}}\n\n</details>\n{{/if}}\n{{/each}}\n{{/unless}}\n<br />\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
 }</pre></td>
   <td></td>
   <td><td>
@@ -772,7 +780,7 @@ location with this method.
   "enabled": false,
   "supportPolicy": ["lts"],
   "branchName": "{{branchPrefix}}node-{{depNameSanitized}}",
-  "prBody": "This Pull Request updates `{{depName}}` versions from `{{currentVersions}}` to `{{newVersions}}`. This is according to the configured node.js support policy \"{{supportPolicy}}\".\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com).",
+  "prBody": "This Pull Request updates {{depName}} versions from `{{currentVersions}}` to `{{newVersions}}`. This is according to the configured node.js support policy \"{{supportPolicy}}\".\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "prTitle": "Update {{depName}} versions to [{{newVersions}}]"
 }</pre></td>
   <td>`RENOVATE_NODE`</td>
@@ -787,7 +795,7 @@ location with this method.
   "branchName": "{{branchPrefix}}docker-{{depNameSanitized}}-{{newVersionMajor}}.x",
   "commitMessage": "Update {{depName}} to tag {{newTag}}",
   "prTitle": "Update {{depName}} Dockerfile tag to {{#if isMajor}}v{{newVersionMajor}}{{else}}v{{newTag}}{{/if}}",
-  "prBody": "This Pull Request updates Docker base image `{{depName}}` from tag `{{currentTag}}` to new tag `{{newTag}}`. For details on Renovate's Docker support, please visit https://renovateapp.com/docs/language-support/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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com).",
+  "prBody": "This Pull Request updates Docker base image {{depName}} from tag `{{currentTag}}` to new tag `{{newTag}}`. For details on Renovate's Docker support, please visit https://renovateapp.com/docs/language-support/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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "major": {"enabled": false},
   "digest": {
     "branchName": "{{branchPrefix}}docker-{{depNameSanitized}}-{{currentTag}}",
@@ -802,12 +810,12 @@ location with this method.
     "groupName": "Pin Docker Digests",
     "group": {
       "prTitle": "Pin Docker digests",
-      "prBody": "This Pull Request pins Dockerfiles to use image digests. For details on Renovate's Docker support, please visit https://renovateapp.com/docs/language-support/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{{#each upgrades as |upgrade|}}\n-   {{#if repositoryUrl}}[{{upgrade.depName}}]({{upgrade.repositoryUrl}}){{else}}`{{depName}}`{{/if}}: `{{upgrade.newDigest}}`\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
+      "prBody": "This Pull Request pins Dockerfiles to use image digests. For details on Renovate's Docker support, please visit https://renovateapp.com/docs/language-support/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{{#each upgrades as |upgrade|}}\n-   {{#if repositoryUrl}}[{{upgrade.depName}}]({{upgrade.repositoryUrl}}){{else}}{{depName}}{{/if}}: `{{upgrade.newDigest}}`\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
     }
   },
   "group": {
     "prTitle": "Update Docker {{groupName}} digests",
-    "prBody": "This Pull Request updates Dockerfiles to use image digests.\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}}: `{{upgrade.newDigest}}`\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
+    "prBody": "This Pull Request updates Dockerfiles to use image digests.\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}}: `{{upgrade.newDigest}}`\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}}\n\n---\n\nThis PR has been generated by [Renovate Bot](https://renovateapp.com)."
   }
 }</pre></td>
   <td>`RENOVATE_DOCKER`</td>
diff --git a/lib/config/definitions.js b/lib/config/definitions.js
index 82fc0db0d5..52f97d50f4 100644
--- a/lib/config/definitions.js
+++ b/lib/config/definitions.js
@@ -197,6 +197,11 @@ const options = [
     cli: false,
     env: false,
   },
+  {
+    name: 'gitAuthor',
+    description: 'Author to use for git commits. RFC5322',
+    type: 'string',
+  },
   {
     name: 'packageFiles',
     description: 'Package file paths',
diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js
index c0df356867..caffe7c989 100644
--- a/lib/platform/github/index.js
+++ b/lib/platform/github/index.js
@@ -1,4 +1,6 @@
 const get = require('./gh-got-wrapper');
+const addrs = require('email-addresses');
+const moment = require('moment');
 
 let config = {};
 
@@ -710,7 +712,8 @@ async function commitFilesToBranch(
   branchName,
   files,
   message,
-  parentBranch = config.baseBranch
+  parentBranch = config.baseBranch,
+  gitAuthor
 ) {
   logger.debug(
     `commitFilesToBranch('${branchName}', files, message, '${parentBranch})'`
@@ -728,7 +731,7 @@ async function commitFilesToBranch(
   }
   // Create tree
   const tree = await createTree(parentTree, fileBlobs);
-  const commit = await createCommit(parentCommit, tree, message);
+  const commit = await createCommit(parentCommit, tree, message, gitAuthor);
   const isBranchExisting = await branchExists(branchName);
   if (isBranchExisting) {
     await updateBranch(branchName, commit);
@@ -815,15 +818,33 @@ async function createTree(baseTree, files) {
 }
 
 // Create a commit and return commit SHA
-async function createCommit(parent, tree, message) {
-  logger.debug(`createCommit(${parent}, ${tree}, ${message})`);
-  return (await get.post(`repos/${config.repoName}/git/commits`, {
-    body: {
-      message,
-      parents: [parent],
-      tree,
-    },
-  })).body.sha;
+async function createCommit(parent, tree, message, gitAuthor) {
+  logger.debug(`createCommit(${parent}, ${tree}, ${message}, ${gitAuthor})`);
+  const now = moment();
+  let author;
+  try {
+    if (gitAuthor) {
+      logger.info({ gitAuthor }, 'Found gitAuthor');
+      const { name, address: email } = addrs.parseOneAddress(gitAuthor);
+      author = {
+        name,
+        email,
+        date: now.format(),
+      };
+    }
+  } catch (err) {
+    logger.warn({ gitAuthor }, 'Error parsing gitAuthor');
+  }
+  const body = {
+    message,
+    parents: [parent],
+    tree,
+  };
+  if (author) {
+    body.author = author;
+  }
+  return (await get.post(`repos/${config.repoName}/git/commits`, { body })).body
+    .sha;
 }
 
 async function getCommitMessages() {
diff --git a/lib/workers/branch/commit.js b/lib/workers/branch/commit.js
index 52000a4888..d88c3582aa 100644
--- a/lib/workers/branch/commit.js
+++ b/lib/workers/branch/commit.js
@@ -25,7 +25,8 @@ async function commitFilesToBranch(config) {
       config.branchName,
       updatedFiles,
       commitMessage,
-      config.parentBranch
+      config.parentBranch,
+      config.gitAuthor
     );
   } else {
     logger.debug(`No files to commit`);
diff --git a/package.json b/package.json
index 37dcfd8827..6e65b4b62a 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,7 @@
     "convert-hrtime": "2.0.0",
     "deepcopy": "0.6.3",
     "detect-indent": "5.0.0",
+    "email-addresses": "3.0.1",
     "fs-extra": "4.0.3",
     "get-installed-path": "4.0.8",
     "gh-got": "7.0.0",
diff --git a/test/manager/__snapshots__/resolve.spec.js.snap b/test/manager/__snapshots__/resolve.spec.js.snap
index 6a61d278bf..edd7fdeb63 100644
--- a/test/manager/__snapshots__/resolve.spec.js.snap
+++ b/test/manager/__snapshots__/resolve.spec.js.snap
@@ -236,6 +236,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -764,6 +765,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -1298,6 +1300,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -2091,6 +2094,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -2622,6 +2626,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -3145,6 +3150,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -3677,6 +3683,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js
index db68df72d6..a1446cc971 100644
--- a/test/platform/github/index.spec.js
+++ b/test/platform/github/index.spec.js
@@ -1308,6 +1308,59 @@ describe('platform/github', () => {
       expect(get.post.mock.calls).toMatchSnapshot();
       expect(get.patch.mock.calls).toMatchSnapshot();
     });
+    it('should parse valid gitAuthor', async () => {
+      // branchExists
+      get.mockImplementationOnce(() => ({
+        body: [
+          {
+            name: 'master',
+          },
+        ],
+      }));
+      const files = [
+        {
+          name: 'package.json',
+          contents: 'hello world',
+        },
+      ];
+      await github.commitFilesToBranch(
+        'the-branch',
+        files,
+        'my other commit message',
+        undefined,
+        'Renovate Bot <bot@renovateapp.com>'
+      );
+      expect(get.post.mock.calls[2][1].body.author.name).toEqual(
+        'Renovate Bot'
+      );
+      expect(get.post.mock.calls[2][1].body.author.email).toEqual(
+        'bot@renovateapp.com'
+      );
+    });
+    it('should skip invalid gitAuthor', async () => {
+      // branchExists
+      get.mockImplementationOnce(() => ({
+        body: [
+          {
+            name: 'master',
+          },
+        ],
+      }));
+      const files = [
+        {
+          name: 'package.json',
+          contents: 'hello world',
+        },
+      ];
+      await github.commitFilesToBranch(
+        'the-branch',
+        files,
+        'my other commit message',
+        undefined,
+        'Renovate Bot bot@renovateapp.com'
+      );
+      expect(get.post.mock.calls[2][1].body.author).toBeUndefined();
+    });
   });
   describe('getCommitMessages()', () => {
     it('returns commits messages', async () => {
diff --git a/test/workers/branch/__snapshots__/commit.spec.js.snap b/test/workers/branch/__snapshots__/commit.spec.js.snap
index 055962807c..18e90bea87 100644
--- a/test/workers/branch/__snapshots__/commit.spec.js.snap
+++ b/test/workers/branch/__snapshots__/commit.spec.js.snap
@@ -12,6 +12,7 @@ Array [
     ],
     "a(b): some commit message",
     undefined,
+    null,
   ],
 ]
 `;
@@ -28,6 +29,7 @@ Array [
     ],
     "some commit message",
     undefined,
+    null,
   ],
 ]
 `;
diff --git a/test/workers/package/__snapshots__/index.spec.js.snap b/test/workers/package/__snapshots__/index.spec.js.snap
index e1b0df5bda..67a27c17a9 100644
--- a/test/workers/package/__snapshots__/index.spec.js.snap
+++ b/test/workers/package/__snapshots__/index.spec.js.snap
@@ -12,6 +12,7 @@ Object {
   "currentVersion": "1.0.0",
   "depName": "foo",
   "description": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Pin Dependencies",
diff --git a/test/workers/repository/updates/__snapshots__/branchify.spec.js.snap b/test/workers/repository/updates/__snapshots__/branchify.spec.js.snap
index d0093a323e..dd64bc651c 100644
--- a/test/workers/repository/updates/__snapshots__/branchify.spec.js.snap
+++ b/test/workers/repository/updates/__snapshots__/branchify.spec.js.snap
@@ -281,6 +281,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -841,6 +842,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -1407,6 +1409,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -1961,6 +1964,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
@@ -2510,6 +2514,7 @@ This PR has been generated by [Renovate Bot](https://renovateapp.com).",
   "excludePackageNames": Array [],
   "excludePackagePatterns": Array [],
   "extends": Array [],
+  "gitAuthor": null,
   "group": Object {
     "branchName": "{{branchPrefix}}{{groupSlug}}",
     "commitMessage": "Renovate {{groupName}} packages",
diff --git a/yarn.lock b/yarn.lock
index 95e404eea5..d848b68c01 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1312,6 +1312,10 @@ editor@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
 
+email-addresses@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.0.1.tgz#c1fc20c189e7f96d4012d375db5feaccdd24391c"
+
 encoding@^0.1.11:
   version "0.1.12"
   resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
-- 
GitLab