diff --git a/README.md b/README.md index 8969446..1e94e04 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,11 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ # Default: true clean: '' + # Whether to preserve local changes during checkout. If true, tries to preserve + # local files that are not tracked by Git. By default, all files will be overwritten. + # Default: false + preserveLocalChanges: '' + # Partially clone against a given filter. Overrides sparse-checkout if set. # Default: null filter: '' @@ -332,6 +337,21 @@ jobs: *NOTE:* The user email is `{user.id}+{user.login}@users.noreply.github.com`. See users API: https://api.github.com/users/github-actions%5Bbot%5D +## Preserve local changes during checkout + +```yaml +steps: + - name: Create file before checkout + shell: pwsh + run: New-Item -Path . -Name "example.txt" -ItemType "File" + + - name: Checkout with preserving local changes + uses: actions/checkout@v5 + with: + clean: false + preserveLocalChanges: true +``` + # Recommended permissions When using the `checkout` action in your GitHub Actions workflow, it is recommended to set the following `GITHUB_TOKEN` permissions to ensure proper functionality, unless alternative auth is provided via the `token` or `ssh-key` inputs: diff --git a/action.yml b/action.yml index 767c416..26d5a5b 100644 --- a/action.yml +++ b/action.yml @@ -56,7 +56,10 @@ inputs: description: 'Relative path under $GITHUB_WORKSPACE to place the repository' clean: description: 'Whether to execute `git clean -ffdx && git reset --hard HEAD` before fetching' - default: true + default: 'true' + preserveLocalChanges: + description: 'Whether to preserve local changes during checkout. If true, tries to preserve local files that are not tracked by Git. By default, all files will be overwritten.' + default: 'false' filter: description: > Partially clone against a given filter. diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8e42a38..26e4102 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -22,7 +22,7 @@ export interface IGitCommandManager { disableSparseCheckout(): Promise sparseCheckout(sparseCheckout: string[]): Promise sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise - checkout(ref: string, startPoint: string): Promise + checkout(ref: string, startPoint: string, options?: string[]): Promise checkoutDetach(): Promise config( configKey: string, @@ -203,8 +203,21 @@ class GitCommandManager { ) } - async checkout(ref: string, startPoint: string): Promise { - const args = ['checkout', '--progress', '--force'] + async checkout( + ref: string, + startPoint: string, + options: string[] = [] + ): Promise { + const args = ['checkout', '--progress'] + + // Add custom options (like --merge) if provided + if (options.length > 0) { + args.push(...options) + } else { + // Default behavior - use force + args.push('--force') + } + if (startPoint) { args.push('-B', ref, startPoint) } else { diff --git a/src/git-source-provider.ts b/src/git-source-provider.ts index 2d35138..7f694a1 100644 --- a/src/git-source-provider.ts +++ b/src/git-source-provider.ts @@ -229,7 +229,15 @@ export async function getSource(settings: IGitSourceSettings): Promise { // Checkout core.startGroup('Checking out the ref') - await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) + if (settings.preserveLocalChanges) { + core.info('Attempting to preserve local changes during checkout') + // Use --merge to preserve local changes if possible + // This will fail if there are merge conflicts, but that's expected behavior + await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint, ['--merge']) + } else { + // Use the default behavior with --force + await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) + } core.endGroup() // Submodules diff --git a/src/git-source-settings.ts b/src/git-source-settings.ts index 4e41ac3..d6b2f44 100644 --- a/src/git-source-settings.ts +++ b/src/git-source-settings.ts @@ -25,10 +25,15 @@ export interface IGitSourceSettings { commit: string /** - * Indicates whether to clean the repository + * Whether to execute git clean and git reset before fetching */ clean: boolean + /** + * Whether to preserve local changes during checkout + */ + preserveLocalChanges: boolean + /** * The filter determining which objects to include */ diff --git a/src/input-helper.ts b/src/input-helper.ts index 059232f..833cdcd 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -82,6 +82,10 @@ export async function getInputs(): Promise { result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE' core.debug(`clean = ${result.clean}`) + // Preserve local changes + result.preserveLocalChanges = (core.getInput('preserveLocalChanges') || 'false').toUpperCase() === 'TRUE' + core.debug(`preserveLocalChanges = ${result.preserveLocalChanges}`) + // Filter const filter = core.getInput('filter') if (filter) {