From d9105a4631593f0dc11f3ae6bfbcb5ac43625523 Mon Sep 17 00:00:00 2001 From: Tonye Jack Date: Fri, 16 Jun 2023 17:52:27 -0600 Subject: [PATCH] feat: add support for recovering deleted files --- action.yml | 8 +++++++ src/inputs.ts | 13 ++++++++++- src/main.ts | 10 +++++++- src/utils.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 7434181073d..4a0b5a6a996 100644 --- a/action.yml +++ b/action.yml @@ -145,6 +145,14 @@ inputs: description: "Output renamed files as deleted and added files." required: false default: "false" + recover_deleted_files_to_original_location: + description: "Recover deleted files to their original location." + required: false + default: "false" + recover_deleted_files_to_destination: + description: "Recover deleted files to the destination directory." + required: false + default: "" outputs: added_files: diff --git a/src/inputs.ts b/src/inputs.ts index 583b27ce8fe..db4697e193a 100644 --- a/src/inputs.ts +++ b/src/inputs.ts @@ -37,6 +37,8 @@ export type Inputs = { writeOutputFiles: boolean outputDir: string outputRenamedFilesAsDeletedAndAdded: boolean + recoverDeletedFiles: boolean + recoverDeletedFilesToDestination: string } export const getInputs = (): Inputs => { @@ -145,6 +147,13 @@ export const getInputs = (): Inputs => { 'output_renamed_files_as_deleted_and_added', {required: false} ) + const recoverDeletedFiles = core.getBooleanInput('recover_deleted_files', { + required: false + }) + const recoverDeletedFilesToDestination = core.getInput( + 'recover_deleted_files_to_destination', + {required: false} + ) const inputs: Inputs = { files, @@ -180,7 +189,9 @@ export const getInputs = (): Inputs => { sinceLastRemoteCommit, writeOutputFiles, outputDir, - outputRenamedFilesAsDeletedAndAdded + outputRenamedFilesAsDeletedAndAdded, + recoverDeletedFiles, + recoverDeletedFilesToDestination } if (fetchDepth) { diff --git a/src/main.ts b/src/main.ts index 5cc91459b12..9dc26d4c8bf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ import * as core from '@actions/core' import path from 'path' -import {getAllDiffFiles, getRenamedFiles} from './changedFiles' +import {ChangeTypeEnum, getAllDiffFiles, getRenamedFiles} from './changedFiles' import {setChangedFilesOutput} from './changedFilesOutput' import { DiffResult, @@ -14,6 +14,7 @@ import { getSubmodulePath, getYamlFilePatterns, isRepoShallow, + recoverDeletedFiles, setOutput, submoduleExists, updateGitGlobalConfig, @@ -118,6 +119,13 @@ export async function run(): Promise { core.info('All Done!') core.endGroup() + recoverDeletedFiles({ + inputs, + workingDirectory, + deletedFiles: allDiffFiles[ChangeTypeEnum.Deleted], + sha: diffResult.currentSha + }) + const filePatterns = await getFilePatterns({ inputs, workingDirectory diff --git a/src/utils.ts b/src/utils.ts index 1ff54603d44..3cdd3e8cdd2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1032,3 +1032,68 @@ export const setOutput = async ({ await fs.writeFile(outputFilePath, cleanedValue.replace(/\\"/g, '"')) } } + +const getDeletedFileContents = async ({ + cwd, + filePath, + sha +}: { + cwd: string + filePath: string + sha: string +}): Promise => { + const {stdout, exitCode, stderr} = await exec.getExecOutput( + 'git', + ['show', `${sha}:${filePath}`], + { + cwd, + silent: process.env.RUNNER_DEBUG !== '1', + ignoreReturnCode: true + } + ) + + if (exitCode !== 0) { + throw new Error( + `Error getting file content from git history "${filePath}": ${stderr}` + ) + } + + return stdout +} + +export const recoverDeletedFiles = async ({ + inputs, + workingDirectory, + deletedFiles, + sha +}: { + inputs: Inputs + workingDirectory: string + deletedFiles: string[] + sha: string +}): Promise => { + if (inputs.recoverDeletedFiles) { + for (const deletedFile of deletedFiles) { + let target = path.join(workingDirectory, deletedFile) + + if (inputs.recoverDeletedFilesToDestination) { + target = path.join( + workingDirectory, + inputs.recoverDeletedFilesToDestination, + deletedFile + ) + } + + const deletedFileContents = await getDeletedFileContents({ + cwd: workingDirectory, + filePath: deletedFile, + sha + }) + + if (!(await exists(path.dirname(target)))) { + await fs.mkdir(path.dirname(target), {recursive: true}) + } + await fs.writeFile(target, deletedFileContents) + } + } +}