mirror of
https://github.com/tj-actions/changed-files
synced 2025-02-11 13:27:42 +00:00
feat: add support for recovering deleted files (#1269)
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
parent
0621d936c0
commit
77f9e6c7c6
7 changed files with 222 additions and 5 deletions
82
.github/workflows/test.yml
vendored
82
.github/workflows/test.yml
vendored
|
@ -601,6 +601,88 @@ jobs:
|
||||||
shell:
|
shell:
|
||||||
bash
|
bash
|
||||||
|
|
||||||
|
test_recover_deleted_file:
|
||||||
|
name: Test changed-files recover deleted file
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
max-parallel: 4
|
||||||
|
matrix:
|
||||||
|
fetch-depth: [0, 1, 2]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout branch
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: ${{ matrix.fetch-depth }}
|
||||||
|
|
||||||
|
- name: Download build assets
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: build-assets
|
||||||
|
|
||||||
|
- name: Run changed-files with recover_deleted_files
|
||||||
|
id: changed-files-recover-deleted-files
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
base_sha: "fcdeb5b3d797752d95f6dbe98552a95c29dad338"
|
||||||
|
sha: "432e0c810c60ef1332850a971c5ec39022034b4c"
|
||||||
|
recover_deleted_files: true
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
run: |
|
||||||
|
echo "${{ toJSON(steps.changed-files-recover-deleted-files.outputs) }}"
|
||||||
|
shell:
|
||||||
|
bash
|
||||||
|
|
||||||
|
- name: Verify deleted files
|
||||||
|
if: steps.changed-files-recover-deleted-files.outputs.deleted_files != 'test/test deleted.txt'
|
||||||
|
run: |
|
||||||
|
echo "Expected: (test/test deleted.txt) got ${{ steps.changed-files-recover-deleted-files.outputs.deleted_files }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Verify that test/test deleted.txt is restored
|
||||||
|
run: |
|
||||||
|
if [ ! -f "test/test deleted.txt" ]; then
|
||||||
|
echo "Expected: (test/test deleted.txt) to exist"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
cat "test/test deleted.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run changed-files with recover_deleted_files and recover_deleted_files_to_destination
|
||||||
|
id: changed-files-recover-deleted-files-to-destination
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
base_sha: "fcdeb5b3d797752d95f6dbe98552a95c29dad338"
|
||||||
|
sha: "432e0c810c60ef1332850a971c5ec39022034b4c"
|
||||||
|
recover_deleted_files: true
|
||||||
|
recover_deleted_files_to_destination: "deleted_files"
|
||||||
|
|
||||||
|
- name: Show output
|
||||||
|
run: |
|
||||||
|
echo "${{ toJSON(steps.changed-files-recover-deleted-files-to-destination.outputs) }}"
|
||||||
|
shell:
|
||||||
|
bash
|
||||||
|
|
||||||
|
- name: Verify deleted files
|
||||||
|
if: steps.changed-files-recover-deleted-files-to-destination.outputs.deleted_files != 'test/test deleted.txt'
|
||||||
|
run: |
|
||||||
|
echo "Expected: (test/test deleted.txt) got ${{ steps.changed-files-recover-deleted-files-to-destination.outputs.deleted_files }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Verify that test/test deleted.txt is restored
|
||||||
|
run: |
|
||||||
|
if [ ! -f "deleted_files/test/test deleted.txt" ]; then
|
||||||
|
echo "Expected: (deleted_files/test/test deleted.txt) to exist"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
cat "deleted_files/test/test deleted.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test changed-files
|
name: Test changed-files
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
|
@ -145,6 +145,14 @@ inputs:
|
||||||
description: "Output renamed files as deleted and added files."
|
description: "Output renamed files as deleted and added files."
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: "false"
|
||||||
|
recover_deleted_files:
|
||||||
|
description: "Recover deleted files."
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
recover_deleted_files_to_destination:
|
||||||
|
description: "Recover deleted files to a new destination directory, defaults to the original location."
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
added_files:
|
added_files:
|
||||||
|
|
47
dist/index.js
generated
vendored
47
dist/index.js
generated
vendored
|
@ -1073,6 +1073,10 @@ const getInputs = () => {
|
||||||
});
|
});
|
||||||
const outputDir = core.getInput('output_dir', { required: false });
|
const outputDir = core.getInput('output_dir', { required: false });
|
||||||
const outputRenamedFilesAsDeletedAndAdded = core.getBooleanInput('output_renamed_files_as_deleted_and_added', { required: false });
|
const outputRenamedFilesAsDeletedAndAdded = core.getBooleanInput('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 = {
|
const inputs = {
|
||||||
files,
|
files,
|
||||||
filesSeparator,
|
filesSeparator,
|
||||||
|
@ -1107,7 +1111,9 @@ const getInputs = () => {
|
||||||
sinceLastRemoteCommit,
|
sinceLastRemoteCommit,
|
||||||
writeOutputFiles,
|
writeOutputFiles,
|
||||||
outputDir,
|
outputDir,
|
||||||
outputRenamedFilesAsDeletedAndAdded
|
outputRenamedFilesAsDeletedAndAdded,
|
||||||
|
recoverDeletedFiles,
|
||||||
|
recoverDeletedFilesToDestination
|
||||||
};
|
};
|
||||||
if (fetchDepth) {
|
if (fetchDepth) {
|
||||||
inputs.fetchDepth = Math.max(parseInt(fetchDepth, 10), 2);
|
inputs.fetchDepth = Math.max(parseInt(fetchDepth, 10), 2);
|
||||||
|
@ -1233,6 +1239,12 @@ function run() {
|
||||||
core.debug(`All diff files: ${JSON.stringify(allDiffFiles)}`);
|
core.debug(`All diff files: ${JSON.stringify(allDiffFiles)}`);
|
||||||
core.info('All Done!');
|
core.info('All Done!');
|
||||||
core.endGroup();
|
core.endGroup();
|
||||||
|
yield (0, utils_1.recoverDeletedFiles)({
|
||||||
|
inputs,
|
||||||
|
workingDirectory,
|
||||||
|
deletedFiles: allDiffFiles[changedFiles_1.ChangeTypeEnum.Deleted],
|
||||||
|
sha: diffResult.previousSha
|
||||||
|
});
|
||||||
const filePatterns = yield (0, utils_1.getFilePatterns)({
|
const filePatterns = yield (0, utils_1.getFilePatterns)({
|
||||||
inputs,
|
inputs,
|
||||||
workingDirectory
|
workingDirectory
|
||||||
|
@ -1367,7 +1379,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.setOutput = exports.getYamlFilePatterns = exports.getFilePatterns = exports.jsonOutput = exports.getDirnameMaxDepth = exports.canDiffCommits = exports.getPreviousGitTag = exports.verifyCommitSha = exports.getParentSha = exports.getRemoteBranchHeadSha = exports.getHeadSha = exports.gitLog = exports.getFilteredChangedFiles = exports.getAllChangedFiles = exports.gitRenamedFiles = exports.gitSubmoduleDiffSHA = exports.getSubmodulePath = exports.gitFetchSubmodules = exports.gitFetch = exports.submoduleExists = exports.isRepoShallow = exports.updateGitGlobalConfig = exports.verifyMinimumGitVersion = void 0;
|
exports.recoverDeletedFiles = exports.setOutput = exports.getYamlFilePatterns = exports.getFilePatterns = exports.jsonOutput = exports.getDirnameMaxDepth = exports.canDiffCommits = exports.getPreviousGitTag = exports.verifyCommitSha = exports.getParentSha = exports.getRemoteBranchHeadSha = exports.getHeadSha = exports.gitLog = exports.getFilteredChangedFiles = exports.getAllChangedFiles = exports.gitRenamedFiles = exports.gitSubmoduleDiffSHA = exports.getSubmodulePath = exports.gitFetchSubmodules = exports.gitFetch = exports.submoduleExists = exports.isRepoShallow = exports.updateGitGlobalConfig = exports.verifyMinimumGitVersion = void 0;
|
||||||
/*global AsyncIterableIterator*/
|
/*global AsyncIterableIterator*/
|
||||||
const core = __importStar(__nccwpck_require__(2186));
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
const exec = __importStar(__nccwpck_require__(1514));
|
const exec = __importStar(__nccwpck_require__(1514));
|
||||||
|
@ -2039,6 +2051,37 @@ const setOutput = ({ key, value, inputs }) => __awaiter(void 0, void 0, void 0,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
exports.setOutput = setOutput;
|
exports.setOutput = setOutput;
|
||||||
|
const getDeletedFileContents = ({ cwd, filePath, sha }) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
const { stdout, exitCode, stderr } = yield 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;
|
||||||
|
});
|
||||||
|
const recoverDeletedFiles = ({ inputs, workingDirectory, deletedFiles, sha }) => __awaiter(void 0, void 0, void 0, function* () {
|
||||||
|
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 = yield getDeletedFileContents({
|
||||||
|
cwd: workingDirectory,
|
||||||
|
filePath: deletedFile,
|
||||||
|
sha
|
||||||
|
});
|
||||||
|
if (!(yield exists(path.dirname(target)))) {
|
||||||
|
yield fs_1.promises.mkdir(path.dirname(target), { recursive: true });
|
||||||
|
}
|
||||||
|
yield fs_1.promises.writeFile(target, deletedFileContents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
exports.recoverDeletedFiles = recoverDeletedFiles;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
|
@ -37,6 +37,8 @@ export type Inputs = {
|
||||||
writeOutputFiles: boolean
|
writeOutputFiles: boolean
|
||||||
outputDir: string
|
outputDir: string
|
||||||
outputRenamedFilesAsDeletedAndAdded: boolean
|
outputRenamedFilesAsDeletedAndAdded: boolean
|
||||||
|
recoverDeletedFiles: boolean
|
||||||
|
recoverDeletedFilesToDestination: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getInputs = (): Inputs => {
|
export const getInputs = (): Inputs => {
|
||||||
|
@ -145,6 +147,13 @@ export const getInputs = (): Inputs => {
|
||||||
'output_renamed_files_as_deleted_and_added',
|
'output_renamed_files_as_deleted_and_added',
|
||||||
{required: false}
|
{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 = {
|
const inputs: Inputs = {
|
||||||
files,
|
files,
|
||||||
|
@ -180,7 +189,9 @@ export const getInputs = (): Inputs => {
|
||||||
sinceLastRemoteCommit,
|
sinceLastRemoteCommit,
|
||||||
writeOutputFiles,
|
writeOutputFiles,
|
||||||
outputDir,
|
outputDir,
|
||||||
outputRenamedFilesAsDeletedAndAdded
|
outputRenamedFilesAsDeletedAndAdded,
|
||||||
|
recoverDeletedFiles,
|
||||||
|
recoverDeletedFilesToDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fetchDepth) {
|
if (fetchDepth) {
|
||||||
|
|
10
src/main.ts
10
src/main.ts
|
@ -1,6 +1,6 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {getAllDiffFiles, getRenamedFiles} from './changedFiles'
|
import {ChangeTypeEnum, getAllDiffFiles, getRenamedFiles} from './changedFiles'
|
||||||
import {setChangedFilesOutput} from './changedFilesOutput'
|
import {setChangedFilesOutput} from './changedFilesOutput'
|
||||||
import {
|
import {
|
||||||
DiffResult,
|
DiffResult,
|
||||||
|
@ -14,6 +14,7 @@ import {
|
||||||
getSubmodulePath,
|
getSubmodulePath,
|
||||||
getYamlFilePatterns,
|
getYamlFilePatterns,
|
||||||
isRepoShallow,
|
isRepoShallow,
|
||||||
|
recoverDeletedFiles,
|
||||||
setOutput,
|
setOutput,
|
||||||
submoduleExists,
|
submoduleExists,
|
||||||
updateGitGlobalConfig,
|
updateGitGlobalConfig,
|
||||||
|
@ -118,6 +119,13 @@ export async function run(): Promise<void> {
|
||||||
core.info('All Done!')
|
core.info('All Done!')
|
||||||
core.endGroup()
|
core.endGroup()
|
||||||
|
|
||||||
|
await recoverDeletedFiles({
|
||||||
|
inputs,
|
||||||
|
workingDirectory,
|
||||||
|
deletedFiles: allDiffFiles[ChangeTypeEnum.Deleted],
|
||||||
|
sha: diffResult.previousSha
|
||||||
|
})
|
||||||
|
|
||||||
const filePatterns = await getFilePatterns({
|
const filePatterns = await getFilePatterns({
|
||||||
inputs,
|
inputs,
|
||||||
workingDirectory
|
workingDirectory
|
||||||
|
|
65
src/utils.ts
65
src/utils.ts
|
@ -1032,3 +1032,68 @@ export const setOutput = async ({
|
||||||
await fs.writeFile(outputFilePath, cleanedValue.replace(/\\"/g, '"'))
|
await fs.writeFile(outputFilePath, cleanedValue.replace(/\\"/g, '"'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDeletedFileContents = async ({
|
||||||
|
cwd,
|
||||||
|
filePath,
|
||||||
|
sha
|
||||||
|
}: {
|
||||||
|
cwd: string
|
||||||
|
filePath: string
|
||||||
|
sha: string
|
||||||
|
}): Promise<string> => {
|
||||||
|
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<void> => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue