From 4a1d769a4b07786ebf3d10ff992ef69781fca146 Mon Sep 17 00:00:00 2001 From: James Ives Date: Tue, 9 Dec 2025 21:26:27 -0500 Subject: [PATCH] fix: provide proper guidance on using persist-credentials --- .github/workflows/integration.yml | 2 ++ README.md | 12 +++---- __tests__/git.test.ts | 48 ------------------------- src/git.ts | 59 ------------------------------- 4 files changed, 8 insertions(+), 113 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f3d02be5d..fa59afaa8 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -36,6 +36,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6.0.1 + with: + persist-credentials: false - name: Build and Deploy uses: JamesIves/github-pages-deploy-action@releases/v4-validate diff --git a/README.md b/README.md index cb22ffdb4..edf02fbb2 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. run: | @@ -104,7 +104,7 @@ By default, the action does not need any token configuration and uses the provid | `branch` | This is the branch you wish to deploy to, for example, `gh-pages` or `docs`. Defaults to `gh-pages`. | `with` | **No** | | `git-config-name` | Allows you to customize the name that is attached to the git config which is used when pushing the deployment commits. If this is not included it will use the name in the GitHub context, followed by the name of the action. | `with` | **No** | | `git-config-email` | Allows you to customize the email that is attached to the git config which is used when pushing the deployment commits. If this is not included it will use the email in the GitHub context, followed by a generic noreply GitHub email. You can include `<>` for the value if you wish to omit this field altogether and push the commits without an email. | `with` | **No** | -| `repository-name` | Allows you to specify a different repository path so long as you have permissions to push to it. This should be formatted like so: `JamesIves/github-pages-deploy-action`. You'll need to use a PAT in the `token` input for this configuration option to work properly. | `with` | **No** | +| `repository-name` | Allows you to specify a different repository path so long as you have permissions to push to it. This should be formatted like so: `JamesIves/github-pages-deploy-action`. You'll need to use a PAT in the `token` input for this configuration option to work properly. **When using `actions/checkout@v6` or later, you must also set `persist-credentials: false` in the checkout step to prevent authentication conflicts.** | `with` | **No** | | `target-folder` | If you'd like to push the contents of the deployment folder into a specific directory on the deployment branch you can specify it here. | `with` | **No** | | `commit-message` | If you need to customize the commit message for an integration you can do so. | `with` | **No** | | `clean` | You can use this option to delete files from your deployment destination that no longer exist in your deployment source. One use case is if your project generates hashed files that vary from build to build. Using `clean` will not affect `.git`, `.github`, or `.ssh` directories. This option is turned on by default and can be toggled off by setting it to `false`. | `with` | **No** | @@ -167,7 +167,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. run: | @@ -217,7 +217,7 @@ jobs: runs-on: windows-latest # The first job utilizes windows-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. run: | @@ -236,7 +236,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download Artifacts 🔻 # The built project is downloaded into the 'site' folder. uses: actions/download-artifact@v1 @@ -290,7 +290,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. run: | diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts index b028f421a..191098b8b 100644 --- a/__tests__/git.test.ts +++ b/__tests__/git.test.ts @@ -146,54 +146,6 @@ describe('git', () => { await init(action) expect(execute).toHaveBeenCalledTimes(9) }) - - it('should remove includeIf git config sections when present', async () => { - // Mock execute to return includeIf config entries - ;(execute as jest.Mock) - .mockImplementationOnce(() => { - // First call: git config safe.directory - return {stdout: '', stderr: ''} - }) - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // user.name - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // user.email - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // core.ignorecase - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // unset extraheader - .mockImplementationOnce(() => { - // git config --local --get-regexp includeIf - simulate checkout@v6 style config - return { - stdout: - 'includeIf.gitdir:/home/runner/work/repo/.git.path /home/runner/work/_temp/git-credentials-123.config\n', - stderr: '' - } - }) - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // remove-section includeIf --local - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // git config --global --get-regexp includeIf - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // git remote rm - .mockImplementationOnce(() => ({stdout: '', stderr: ''})) // git remote add - - Object.assign(action, { - hostname: 'github.com', - silent: false, - repositoryPath: 'JamesIves/github-pages-deploy-action', - token: '123', - branch: 'branch', - folder: '.', - pusher: { - name: 'asd', - email: 'as@cat' - }, - isTest: TestFlag.HAS_CHANGED_FILES - }) - - await init(action) - - // Verify that git config --remove-section was called for includeIf in both scopes - expect(execute).toHaveBeenCalledWith( - expect.stringContaining('git config --local --remove-section'), - action.workspace, - true - ) - }) }) describe('deploy', () => { diff --git a/src/git.ts b/src/git.ts index df2e9903a..c754de21e 100644 --- a/src/git.ts +++ b/src/git.ts @@ -76,65 +76,6 @@ export async function init(action: ActionInterface): Promise { ) } - // Remove includeIf directives that point to credential files (actions/checkout@v6+) - // This runs unconditionally because checkout@v6 credentials must be cleared - try { - /* actions/checkout@v6+ uses includeIf directives to inject credentials. - We need to remove these to ensure the provided token/SSH key is used instead. - Check local, global, and system scopes as containers may configure differently. - */ - info('Checking for includeIf credential directives from actions/checkout@v6...') - let foundAny = false - - for (const scope of ['--local', '--global', '--system']) { - try { - const includeIfResult = await execute( - `git config ${scope} --get-regexp 'includeIf\\..*\\.path'`, - action.workspace, - true // Always silent to avoid exposing credential paths - ) - - // Parse the output to find includeIf sections - if (includeIfResult.stdout) { - const lines = includeIfResult.stdout.trim().split('\n') - for (const line of lines) { - // Skip empty lines - if (!line.trim()) { - continue - } - // Each line is in format: includeIf.gitdir:/path/.git.path /path/to/config - // The regex captures the section name without the trailing .path suffix - const match = line.match(/^(includeIf\.[^\s]+)\.path\s+/) - if (match) { - const section = match[1] - foundAny = true - info(`Found includeIf directive in ${scope} scope: ${section}`) - try { - await execute( - `git config ${scope} --remove-section "${section}"`, - action.workspace, - true // Always silent - ) - info(`Removed includeIf section: ${section}`) - } catch (error) { - info(`Failed to remove includeIf section ${section}: ${extractErrorMessage(error)}`) - } - } - } - } - } catch (error) { - // Log but continue - this is expected if no config exists in this scope - info(`No includeIf directives found in ${scope} scope (or scope not accessible)`) - } - } - - if (!foundAny) { - info('No includeIf credential directives found') - } - } catch (error) { - info(`Error while checking for includeIf directives: ${extractErrorMessage(error)}`) - } - try { await execute(`git remote rm origin`, action.workspace, action.silent)