diff --git a/README.md b/README.md
index cb835570..c7c0cb3b 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Here are all the inputs [repo-file-sync-action](https://github.com/BetaHuhn/repo
| `ASSIGNEES` | People to assign to the pull request | **No** | N/A |
| `COMMIT_PREFIX` | Prefix for commit message and pull request title | **No** | 🔄 |
| `COMMIT_BODY` | Commit message body. Will be appended to commit message, separated by two line returns. | **No** | '' |
+| `ORIGINAL_MESSAGE` | Use original commit message instead. Only works if the file(s) where changed and the action was triggered by pushing a single commit. | **No** | false |
| `COMMIT_EACH_FILE` | Commit each file seperately | **No** | true |
| `GIT_EMAIL` | The e-mail address used to commit the synced files | **Only when using installation token** | the email of the PAT used |
| `GIT_USERNAME` | The username used to commit the synced files | **Only when using installation token** | the username of the PAT used |
diff --git a/action.yml b/action.yml
index ff6931f2..4d4b2d33 100644
--- a/action.yml
+++ b/action.yml
@@ -63,6 +63,10 @@ inputs:
description: |
Overwrite any existing Sync PR with the new changes. Defaults to true
required: false
+ ORIGINAL_MESSAGE:
+ description: |
+ Re-use the original commit message for commits. Works only if the action is triggered by pushing one commit. Defaults to false
+ required: false
SKIP_PR:
description: |
Skips creating a Pull Request and pushes directly to the default branch. Defaults to false
diff --git a/src/config.js b/src/config.js
index 3d7a3fdf..f4e10b08 100644
--- a/src/config.js
+++ b/src/config.js
@@ -95,6 +95,11 @@ try {
type: 'boolean',
default: false
}),
+ ORIGINAL_MESSAGE: getInput({
+ key: 'ORIGINAL_MESSAGE',
+ type: 'boolean',
+ default: false
+ }),
BRANCH_PREFIX: getInput({
key: 'BRANCH_PREFIX',
default: 'repo-sync/SOURCE_REPO_NAME'
diff --git a/src/git.js b/src/git.js
index 8ac13216..6d620f91 100644
--- a/src/git.js
+++ b/src/git.js
@@ -1,5 +1,6 @@
const { parse } = require('@putout/git-status-porcelain')
const core = require('@actions/core')
+const github = require('@actions/github')
const { GitHub, getOctokitOptions } = require('@actions/github/lib/utils')
const { throttling } = require('@octokit/plugin-throttling')
const path = require('path')
@@ -127,6 +128,58 @@ class Git {
)
}
+ isOneCommitPush() {
+ return github.context.eventName === 'push' && github.context.payload.commits.length === 1
+ }
+
+ originalCommitMessage() {
+ return github.context.payload.commits[0].message
+ }
+
+ parseGitDiffOutput(string) { // parses git diff output and returns a dictionary mapping the file path to the diff output for this file
+ // split diff into separate entries for separate files. \ndiff --git should be a reliable way to detect the separation, as content of files is always indented
+ return `\n${ string }`.split('\ndiff --git').slice(1).reduce((resultDict, fileDiff) => {
+ const lines = fileDiff.split('\n')
+ const lastHeaderLineIndex = lines.findIndex((line) => line.startsWith('+++'))
+ const plainDiff = lines.slice(lastHeaderLineIndex + 1).join('\n').trim()
+ let filePath = ''
+ if (lines[lastHeaderLineIndex].startsWith('+++ b/')) { // every file except removed files
+ filePath = lines[lastHeaderLineIndex].slice(6) // remove '+++ b/'
+ } else { // for removed file need to use header line with filename before deletion
+ filePath = lines[lastHeaderLineIndex - 1].slice(6) // remove '--- a/'
+ }
+ return { ...resultDict, [filePath]: plainDiff }
+ }, {})
+ }
+
+ async getChangesFromLastCommit(source) { // gets array of git diffs for the source, which either can be a file or a dict
+ if (this.lastCommitChanges === undefined) {
+ const diff = await this.github.repos.compareCommits({
+ mediaType: {
+ format: 'diff'
+ },
+ owner: github.context.payload.repository.owner.name,
+ repo: github.context.payload.repository.name,
+ base: github.context.payload.before,
+ head: github.context.payload.after
+ })
+ this.lastCommitChanges = this.parseGitDiffOutput(diff.data)
+ }
+ if (source.endsWith('/')) {
+ return Object.keys(this.lastCommitChanges).filter((filePath) => filePath.startsWith(source)).reduce((result, key) => [ ...result, this.lastCommitChanges[key] ], [])
+ } else {
+ return this.lastCommitChanges[source] === undefined ? [] : [ this.lastCommitChanges[source] ]
+ }
+ }
+
+ async changes(destination) { // gets array of git diffs for the destination, which either can be a file or a dict
+ const output = await execCmd(
+ `git diff HEAD ${ destination }`,
+ this.workingDir
+ )
+ return Object.values(this.parseGitDiffOutput(output))
+ }
+
async hasChanges() {
const statusOutput = await execCmd(
`git status --porcelain`,
@@ -142,7 +195,7 @@ class Git {
message += `\n\n${ COMMIT_BODY }`
}
return execCmd(
- `git commit -m "${ message }"`,
+ `git commit -m "${ message.replace(/"/g, '\\"') }"`,
this.workingDir
)
}
diff --git a/src/helpers.js b/src/helpers.js
index ddb81da9..487f64be 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -98,6 +98,8 @@ const remove = async (src) => {
return fs.remove(src)
}
+const arrayEquals = (array1, array2) => Array.isArray(array1) && Array.isArray(array2) && array1.length === array2.length && array1.every((value, i) => value === array2[i])
+
module.exports = {
forEach,
dedent,
@@ -105,5 +107,6 @@ module.exports = {
pathIsDirectory,
execCmd,
copy,
- remove
+ remove,
+ arrayEquals
}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 07a22ef9..2dffa9cf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,7 @@ const core = require('@actions/core')
const fs = require('fs')
const Git = require('./git')
-const { forEach, dedent, addTrailingSlash, pathIsDirectory, copy, remove } = require('./helpers')
+const { forEach, dedent, addTrailingSlash, pathIsDirectory, copy, remove, arrayEquals } = require('./helpers')
const {
parseConfig,
@@ -14,7 +14,8 @@ const {
TMP_DIR,
SKIP_CLEANUP,
OVERWRITE_EXISTING_PR,
- SKIP_PR
+ SKIP_PR,
+ ORIGINAL_MESSAGE
} = require('./config')
const run = async () => {
@@ -82,14 +83,15 @@ const run = async () => {
// Use different commit/pr message based on if the source is a directory or file
const directory = isDirectory ? 'directory' : ''
const otherFiles = isDirectory ? 'and copied all sub files/folders' : ''
+ const useOriginalCommitMessage = ORIGINAL_MESSAGE && git.isOneCommitPush() && arrayEquals(await git.getChangesFromLastCommit(file.source), await git.changes(file.dest))
const message = {
true: {
- commit: `${ COMMIT_PREFIX } Synced local '${ file.dest }' with remote '${ file.source }'`,
+ commit: useOriginalCommitMessage ? git.originalCommitMessage() : `${ COMMIT_PREFIX } Synced local '${ file.dest }' with remote '${ file.source }'`,
pr: `Synced local ${ directory } ${ file.dest }
with remote ${ directory } ${ file.source }
`
},
false: {
- commit: `${ COMMIT_PREFIX } Created local '${ file.dest }' from remote '${ file.source }'`,
+ commit: useOriginalCommitMessage ? git.originalCommitMessage() : `${ COMMIT_PREFIX } Created local '${ file.dest }' from remote '${ file.source }'`,
pr: `Created local ${ directory } ${ file.dest }
${ otherFiles } from remote ${ directory } ${ file.source }
`
}
}
@@ -128,7 +130,14 @@ const run = async () => {
if (hasChanges === true) {
core.debug(`Creating commit for remaining files`)
- await git.commit()
+ let useOriginalCommitMessage = ORIGINAL_MESSAGE && git.isOneCommitPush()
+ if (useOriginalCommitMessage) {
+ await forEach(item.files, async (file) => {
+ useOriginalCommitMessage = useOriginalCommitMessage && arrayEquals(await git.getChangesFromLastCommit(file.source), await git.changes(file.dest))
+ })
+ }
+
+ await git.commit(useOriginalCommitMessage ? git.originalCommitMessage() : undefined)
modified.push({
dest: git.workingDir
})