mirror of
				https://github.com/actions/checkout.git
				synced 2025-10-31 07:30:32 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			v4.3.0
			...
			b13eccf351
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | b13eccf351 | ||
|   | 82257b56c2 | ||
|   | d9b320ec70 | ||
|   | bcc5319a0b | ||
|   | ff7abcd0c3 | ||
|   | 08c6903cd8 | ||
|   | 9f265659d3 | 
							
								
								
									
										4
									
								
								.github/workflows/check-dist.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/check-dist.yml
									
									
									
									
										vendored
									
									
								
							| @@ -24,10 +24,10 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4.1.6 |       - uses: actions/checkout@v4.1.6 | ||||||
|  |  | ||||||
|       - name: Set Node.js 20.x |       - name: Set Node.js 24.x | ||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20.x |           node-version: 24.x | ||||||
|  |  | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: npm ci |         run: npm ci | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,7 @@ jobs: | |||||||
|     steps: |     steps: | ||||||
|       - uses: actions/setup-node@v4 |       - uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 20.x |           node-version: 24.x | ||||||
|       - uses: actions/checkout@v4.1.6 |       - uses: actions/checkout@v4.1.6 | ||||||
|       - run: npm ci |       - run: npm ci | ||||||
|       - run: npm run build |       - run: npm run build | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/workflows/update-main-version.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/update-main-version.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,6 +11,7 @@ on: | |||||||
|         type: choice |         type: choice | ||||||
|         description: The major version to update |         description: The major version to update | ||||||
|         options: |         options: | ||||||
|  |           - v5 | ||||||
|           - v4 |           - v4 | ||||||
|           - v3 |           - v3 | ||||||
|           - v2 |           - v2 | ||||||
|   | |||||||
| @@ -1,5 +1,9 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## V5.0.0 | ||||||
|  | * Update actions checkout to use node 24 by @salmanmkc in https://github.com/actions/checkout/pull/2226 | ||||||
|  |  | ||||||
|  |  | ||||||
| ## V4.3.0 | ## V4.3.0 | ||||||
| * docs: update README.md by @motss in https://github.com/actions/checkout/pull/1971 | * docs: update README.md by @motss in https://github.com/actions/checkout/pull/1971 | ||||||
| * Add internal repos for checking out multiple repositories by @mouismail in https://github.com/actions/checkout/pull/1977 | * Add internal repos for checking out multiple repositories by @mouismail in https://github.com/actions/checkout/pull/1977 | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,5 +1,13 @@ | |||||||
| [](https://github.com/actions/checkout/actions/workflows/test.yml) | [](https://github.com/actions/checkout/actions/workflows/test.yml) | ||||||
|  |  | ||||||
|  | # Checkout V5 | ||||||
|  |  | ||||||
|  | ## What's new | ||||||
|  |  | ||||||
|  | - Updated to the node24 runtime | ||||||
|  |   - This requires a minimum Actions Runner version of [v2.327.1](https://github.com/actions/runner/releases/tag/v2.327.1) to run. | ||||||
|  |  | ||||||
|  |  | ||||||
| # Checkout V4 | # Checkout V4 | ||||||
|  |  | ||||||
| This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. | This action checks-out your repository under `$GITHUB_WORKSPACE`, so your workflow can access it. | ||||||
| @@ -36,7 +44,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
|  |  | ||||||
| <!-- start usage --> | <!-- start usage --> | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     # Repository name with owner. For example, actions/checkout |     # Repository name with owner. For example, actions/checkout | ||||||
|     # Default: ${{ github.repository }} |     # Default: ${{ github.repository }} | ||||||
| @@ -149,24 +157,33 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
|  |  | ||||||
| # Scenarios | # Scenarios | ||||||
|  |  | ||||||
| - [Fetch only the root files](#Fetch-only-the-root-files) | - [Checkout V5](#checkout-v5) | ||||||
| - [Fetch only the root files and `.github` and `src` folder](#Fetch-only-the-root-files-and-github-and-src-folder) |   - [What's new](#whats-new) | ||||||
| - [Fetch only a single file](#Fetch-only-a-single-file) | - [Checkout V4](#checkout-v4) | ||||||
| - [Fetch all history for all tags and branches](#Fetch-all-history-for-all-tags-and-branches) |     - [Note](#note) | ||||||
| - [Checkout a different branch](#Checkout-a-different-branch) | - [What's new](#whats-new-1) | ||||||
| - [Checkout HEAD^](#Checkout-HEAD) | - [Usage](#usage) | ||||||
| - [Checkout multiple repos (side by side)](#Checkout-multiple-repos-side-by-side) | - [Scenarios](#scenarios) | ||||||
| - [Checkout multiple repos (nested)](#Checkout-multiple-repos-nested) |   - [Fetch only the root files](#fetch-only-the-root-files) | ||||||
| - [Checkout multiple repos (private)](#Checkout-multiple-repos-private) |   - [Fetch only the root files and `.github` and `src` folder](#fetch-only-the-root-files-and-github-and-src-folder) | ||||||
| - [Checkout pull request HEAD commit instead of merge commit](#Checkout-pull-request-HEAD-commit-instead-of-merge-commit) |   - [Fetch only a single file](#fetch-only-a-single-file) | ||||||
| - [Checkout pull request on closed event](#Checkout-pull-request-on-closed-event) |   - [Fetch all history for all tags and branches](#fetch-all-history-for-all-tags-and-branches) | ||||||
| - [Push a commit using the built-in token](#Push-a-commit-using-the-built-in-token) |   - [Checkout a different branch](#checkout-a-different-branch) | ||||||
| - [Push a commit to a PR using the built-in token](#Push-a-commit-to-a-PR-using-the-built-in-token) |   - [Checkout HEAD^](#checkout-head) | ||||||
|  |   - [Checkout multiple repos (side by side)](#checkout-multiple-repos-side-by-side) | ||||||
|  |   - [Checkout multiple repos (nested)](#checkout-multiple-repos-nested) | ||||||
|  |   - [Checkout multiple repos (private)](#checkout-multiple-repos-private) | ||||||
|  |   - [Checkout pull request HEAD commit instead of merge commit](#checkout-pull-request-head-commit-instead-of-merge-commit) | ||||||
|  |   - [Checkout pull request on closed event](#checkout-pull-request-on-closed-event) | ||||||
|  |   - [Push a commit using the built-in token](#push-a-commit-using-the-built-in-token) | ||||||
|  |   - [Push a commit to a PR using the built-in token](#push-a-commit-to-a-pr-using-the-built-in-token) | ||||||
|  | - [Recommended permissions](#recommended-permissions) | ||||||
|  | - [License](#license) | ||||||
|  |  | ||||||
| ## Fetch only the root files | ## Fetch only the root files | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     sparse-checkout: . |     sparse-checkout: . | ||||||
| ``` | ``` | ||||||
| @@ -174,7 +191,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Fetch only the root files and `.github` and `src` folder | ## Fetch only the root files and `.github` and `src` folder | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     sparse-checkout: | |     sparse-checkout: | | ||||||
|       .github |       .github | ||||||
| @@ -184,7 +201,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Fetch only a single file | ## Fetch only a single file | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     sparse-checkout: | |     sparse-checkout: | | ||||||
|       README.md |       README.md | ||||||
| @@ -194,7 +211,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Fetch all history for all tags and branches | ## Fetch all history for all tags and branches | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     fetch-depth: 0 |     fetch-depth: 0 | ||||||
| ``` | ``` | ||||||
| @@ -202,7 +219,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Checkout a different branch | ## Checkout a different branch | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     ref: my-branch |     ref: my-branch | ||||||
| ``` | ``` | ||||||
| @@ -210,7 +227,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Checkout HEAD^ | ## Checkout HEAD^ | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     fetch-depth: 2 |     fetch-depth: 2 | ||||||
| - run: git checkout HEAD^ | - run: git checkout HEAD^ | ||||||
| @@ -220,12 +237,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - name: Checkout | - name: Checkout | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     path: main |     path: main | ||||||
|  |  | ||||||
| - name: Checkout tools repo | - name: Checkout tools repo | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     repository: my-org/my-tools |     repository: my-org/my-tools | ||||||
|     path: my-tools |     path: my-tools | ||||||
| @@ -236,10 +253,10 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - name: Checkout | - name: Checkout | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|  |  | ||||||
| - name: Checkout tools repo | - name: Checkout tools repo | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     repository: my-org/my-tools |     repository: my-org/my-tools | ||||||
|     path: my-tools |     path: my-tools | ||||||
| @@ -250,12 +267,12 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - name: Checkout | - name: Checkout | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     path: main |     path: main | ||||||
|  |  | ||||||
| - name: Checkout private tools | - name: Checkout private tools | ||||||
|   uses: actions/checkout@v4 |   uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     repository: my-org/my-private-tools |     repository: my-org/my-private-tools | ||||||
|     token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT |     token: ${{ secrets.GH_PAT }} # `GH_PAT` is a secret that contains your PAT | ||||||
| @@ -268,7 +285,7 @@ Please refer to the [release page](https://github.com/actions/checkout/releases/ | |||||||
| ## Checkout pull request HEAD commit instead of merge commit | ## Checkout pull request HEAD commit instead of merge commit | ||||||
|  |  | ||||||
| ```yaml | ```yaml | ||||||
| - uses: actions/checkout@v4 | - uses: actions/checkout@v5 | ||||||
|   with: |   with: | ||||||
|     ref: ${{ github.event.pull_request.head.sha }} |     ref: ${{ github.event.pull_request.head.sha }} | ||||||
| ``` | ``` | ||||||
| @@ -284,7 +301,7 @@ jobs: | |||||||
|   build: |   build: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v5 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ## Push a commit using the built-in token | ## Push a commit using the built-in token | ||||||
| @@ -295,7 +312,7 @@ jobs: | |||||||
|   build: |   build: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v5 | ||||||
|       - run: | |       - run: | | ||||||
|           date > generated.txt |           date > generated.txt | ||||||
|           # Note: the following account information will not work on GHES |           # Note: the following account information will not work on GHES | ||||||
| @@ -317,7 +334,7 @@ jobs: | |||||||
|   build: |   build: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v5 | ||||||
|         with: |         with: | ||||||
|           ref: ${{ github.head_ref }} |           ref: ${{ github.head_ref }} | ||||||
|       - run: | |       - run: | | ||||||
|   | |||||||
| @@ -86,16 +86,29 @@ describe('git-auth-helper tests', () => { | |||||||
|     // Act |     // Act | ||||||
|     await authHelper.configureAuth() |     await authHelper.configureAuth() | ||||||
|  |  | ||||||
|     // Assert config |     // Assert config - check that .git/config contains includeIf entries | ||||||
|     const configContent = ( |     const localConfigContent = ( | ||||||
|       await fs.promises.readFile(localGitConfigPath) |       await fs.promises.readFile(localGitConfigPath) | ||||||
|     ).toString() |     ).toString() | ||||||
|  |     expect( | ||||||
|  |       localConfigContent.indexOf('includeIf.gitdir:') | ||||||
|  |     ).toBeGreaterThanOrEqual(0) | ||||||
|  |  | ||||||
|  |     // Assert credentials config file contains the actual credentials | ||||||
|  |     const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|  |       f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |     ) | ||||||
|  |     expect(credentialsFiles.length).toBe(1) | ||||||
|  |     const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0]) | ||||||
|  |     const credentialsContent = ( | ||||||
|  |       await fs.promises.readFile(credentialsConfigPath) | ||||||
|  |     ).toString() | ||||||
|     const basicCredential = Buffer.from( |     const basicCredential = Buffer.from( | ||||||
|       `x-access-token:${settings.authToken}`, |       `x-access-token:${settings.authToken}`, | ||||||
|       'utf8' |       'utf8' | ||||||
|     ).toString('base64') |     ).toString('base64') | ||||||
|     expect( |     expect( | ||||||
|       configContent.indexOf( |       credentialsContent.indexOf( | ||||||
|         `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` |         `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}` | ||||||
|       ) |       ) | ||||||
|     ).toBeGreaterThanOrEqual(0) |     ).toBeGreaterThanOrEqual(0) | ||||||
| @@ -120,7 +133,7 @@ describe('git-auth-helper tests', () => { | |||||||
|     'inject https://github.com as github server url' |     'inject https://github.com as github server url' | ||||||
|   it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => { |   it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => { | ||||||
|     await testAuthHeader( |     await testAuthHeader( | ||||||
|       configureAuth_AcceptsGitHubServerUrl, |       configureAuth_AcceptsGitHubServerUrlSetToGHEC, | ||||||
|       'https://github.com' |       'https://github.com' | ||||||
|     ) |     ) | ||||||
|   }) |   }) | ||||||
| @@ -141,12 +154,17 @@ describe('git-auth-helper tests', () => { | |||||||
|       // Act |       // Act | ||||||
|       await authHelper.configureAuth() |       await authHelper.configureAuth() | ||||||
|  |  | ||||||
|       // Assert config |       // Assert config - check credentials config file (not local .git/config) | ||||||
|       const configContent = ( |       const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|         await fs.promises.readFile(localGitConfigPath) |         f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |       ) | ||||||
|  |       expect(credentialsFiles.length).toBe(1) | ||||||
|  |       const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0]) | ||||||
|  |       const credentialsContent = ( | ||||||
|  |         await fs.promises.readFile(credentialsConfigPath) | ||||||
|       ).toString() |       ).toString() | ||||||
|       expect( |       expect( | ||||||
|         configContent.indexOf( |         credentialsContent.indexOf( | ||||||
|           `http.https://github.com/.extraheader AUTHORIZATION` |           `http.https://github.com/.extraheader AUTHORIZATION` | ||||||
|         ) |         ) | ||||||
|       ).toBeGreaterThanOrEqual(0) |       ).toBeGreaterThanOrEqual(0) | ||||||
| @@ -251,13 +269,16 @@ describe('git-auth-helper tests', () => { | |||||||
|       expectedSshCommand |       expectedSshCommand | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     // Asserty git config |     // Assert git config | ||||||
|     const gitConfigLines = (await fs.promises.readFile(localGitConfigPath)) |     const gitConfigLines = (await fs.promises.readFile(localGitConfigPath)) | ||||||
|       .toString() |       .toString() | ||||||
|       .split('\n') |       .split('\n') | ||||||
|       .filter(x => x) |       .filter(x => x) | ||||||
|     expect(gitConfigLines).toHaveLength(1) |     // Should have includeIf entries pointing to credentials file | ||||||
|     expect(gitConfigLines[0]).toMatch(/^http\./) |     expect(gitConfigLines.length).toBeGreaterThan(0) | ||||||
|  |     expect( | ||||||
|  |       gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0) | ||||||
|  |     ).toBeTruthy() | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   const configureAuth_setsSshCommandWhenPersistCredentialsTrue = |   const configureAuth_setsSshCommandWhenPersistCredentialsTrue = | ||||||
| @@ -419,8 +440,20 @@ describe('git-auth-helper tests', () => { | |||||||
|     expect( |     expect( | ||||||
|       configContent.indexOf('value-from-global-config') |       configContent.indexOf('value-from-global-config') | ||||||
|     ).toBeGreaterThanOrEqual(0) |     ).toBeGreaterThanOrEqual(0) | ||||||
|  |     // Global config should have include.path pointing to credentials file | ||||||
|  |     expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0) | ||||||
|  |  | ||||||
|  |     // Check credentials in the separate config file | ||||||
|  |     const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|  |       f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |     ) | ||||||
|  |     expect(credentialsFiles.length).toBeGreaterThan(0) | ||||||
|  |     const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0]) | ||||||
|  |     const credentialsContent = ( | ||||||
|  |       await fs.promises.readFile(credentialsConfigPath) | ||||||
|  |     ).toString() | ||||||
|     expect( |     expect( | ||||||
|       configContent.indexOf( |       credentialsContent.indexOf( | ||||||
|         `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` |         `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` | ||||||
|       ) |       ) | ||||||
|     ).toBeGreaterThanOrEqual(0) |     ).toBeGreaterThanOrEqual(0) | ||||||
| @@ -463,8 +496,20 @@ describe('git-auth-helper tests', () => { | |||||||
|       const configContent = ( |       const configContent = ( | ||||||
|         await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) |         await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) | ||||||
|       ).toString() |       ).toString() | ||||||
|  |       // Global config should have include.path pointing to credentials file | ||||||
|  |       expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0) | ||||||
|  |  | ||||||
|  |       // Check credentials in the separate config file | ||||||
|  |       const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|  |         f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |       ) | ||||||
|  |       expect(credentialsFiles.length).toBeGreaterThan(0) | ||||||
|  |       const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0]) | ||||||
|  |       const credentialsContent = ( | ||||||
|  |         await fs.promises.readFile(credentialsConfigPath) | ||||||
|  |       ).toString() | ||||||
|       expect( |       expect( | ||||||
|         configContent.indexOf( |         credentialsContent.indexOf( | ||||||
|           `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` |           `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` | ||||||
|         ) |         ) | ||||||
|       ).toBeGreaterThanOrEqual(0) |       ).toBeGreaterThanOrEqual(0) | ||||||
| @@ -660,19 +705,35 @@ describe('git-auth-helper tests', () => { | |||||||
|     await setup(removeAuth_removesToken) |     await setup(removeAuth_removesToken) | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|     await authHelper.configureAuth() |     await authHelper.configureAuth() | ||||||
|     let gitConfigContent = ( |  | ||||||
|  |     // Sanity check - verify includeIf entries exist in local config | ||||||
|  |     let localConfigContent = ( | ||||||
|       await fs.promises.readFile(localGitConfigPath) |       await fs.promises.readFile(localGitConfigPath) | ||||||
|     ).toString() |     ).toString() | ||||||
|     expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check |     expect( | ||||||
|  |       localConfigContent.indexOf('includeIf.gitdir:') | ||||||
|  |     ).toBeGreaterThanOrEqual(0) | ||||||
|  |  | ||||||
|  |     // Sanity check - verify credentials file exists | ||||||
|  |     let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|  |       f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |     ) | ||||||
|  |     expect(credentialsFiles.length).toBe(1) | ||||||
|  |  | ||||||
|     // Act |     // Act | ||||||
|     await authHelper.removeAuth() |     await authHelper.removeAuth() | ||||||
|  |  | ||||||
|     // Assert git config |     // Assert includeIf entries removed from local git config | ||||||
|     gitConfigContent = ( |     localConfigContent = ( | ||||||
|       await fs.promises.readFile(localGitConfigPath) |       await fs.promises.readFile(localGitConfigPath) | ||||||
|     ).toString() |     ).toString() | ||||||
|     expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) |     expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0) | ||||||
|  |  | ||||||
|  |     // Assert credentials config file deleted | ||||||
|  |     credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter( | ||||||
|  |       f => f.startsWith('git-credentials-') && f.endsWith('.config') | ||||||
|  |     ) | ||||||
|  |     expect(credentialsFiles.length).toBe(0) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   const removeGlobalConfig_removesOverride = |   const removeGlobalConfig_removesOverride = | ||||||
| @@ -733,10 +794,20 @@ async function setup(testName: string): Promise<void> { | |||||||
|     checkout: jest.fn(), |     checkout: jest.fn(), | ||||||
|     checkoutDetach: jest.fn(), |     checkoutDetach: jest.fn(), | ||||||
|     config: jest.fn( |     config: jest.fn( | ||||||
|       async (key: string, value: string, globalConfig?: boolean) => { |       async ( | ||||||
|         const configPath = globalConfig |         key: string, | ||||||
|  |         value: string, | ||||||
|  |         globalConfig?: boolean, | ||||||
|  |         add?: boolean, | ||||||
|  |         configFile?: string | ||||||
|  |       ) => { | ||||||
|  |         const configPath = | ||||||
|  |           configFile || | ||||||
|  |           (globalConfig | ||||||
|             ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') |             ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') | ||||||
|           : localGitConfigPath |             : localGitConfigPath) | ||||||
|  |         // Ensure directory exists | ||||||
|  |         await fs.promises.mkdir(path.dirname(configPath), {recursive: true}) | ||||||
|         await fs.promises.appendFile(configPath, `\n${key} ${value}`) |         await fs.promises.appendFile(configPath, `\n${key} ${value}`) | ||||||
|       } |       } | ||||||
|     ), |     ), | ||||||
| @@ -830,6 +901,7 @@ async function setup(testName: string): Promise<void> { | |||||||
|  |  | ||||||
| async function getActualSshKeyPath(): Promise<string> { | async function getActualSshKeyPath(): Promise<string> { | ||||||
|   let actualTempFiles = (await fs.promises.readdir(runnerTemp)) |   let actualTempFiles = (await fs.promises.readdir(runnerTemp)) | ||||||
|  |     .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file | ||||||
|     .sort() |     .sort() | ||||||
|     .map(x => path.join(runnerTemp, x)) |     .map(x => path.join(runnerTemp, x)) | ||||||
|   if (actualTempFiles.length === 0) { |   if (actualTempFiles.length === 0) { | ||||||
| @@ -843,6 +915,7 @@ async function getActualSshKeyPath(): Promise<string> { | |||||||
|  |  | ||||||
| async function getActualSshKnownHostsPath(): Promise<string> { | async function getActualSshKnownHostsPath(): Promise<string> { | ||||||
|   let actualTempFiles = (await fs.promises.readdir(runnerTemp)) |   let actualTempFiles = (await fs.promises.readdir(runnerTemp)) | ||||||
|  |     .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file | ||||||
|     .sort() |     .sort() | ||||||
|     .map(x => path.join(runnerTemp, x)) |     .map(x => path.join(runnerTemp, x)) | ||||||
|   if (actualTempFiles.length === 0) { |   if (actualTempFiles.length === 0) { | ||||||
|   | |||||||
| @@ -104,6 +104,6 @@ outputs: | |||||||
|   commit: |   commit: | ||||||
|     description: 'The commit SHA that was checked out' |     description: 'The commit SHA that was checked out' | ||||||
| runs: | runs: | ||||||
|   using: node20 |   using: node24 | ||||||
|   main: dist/index.js |   main: dist/index.js | ||||||
|   post: dist/index.js |   post: dist/index.js | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										101
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @@ -162,6 +162,8 @@ class GitAuthHelper { | |||||||
|         this.sshKeyPath = ''; |         this.sshKeyPath = ''; | ||||||
|         this.sshKnownHostsPath = ''; |         this.sshKnownHostsPath = ''; | ||||||
|         this.temporaryHomePath = ''; |         this.temporaryHomePath = ''; | ||||||
|  |         this.credentialsConfigPath = ''; // Path to separate credentials config file in RUNNER_TEMP
 | ||||||
|  |         this.credentialsIncludeKeys = []; // Track includeIf/include config keys for cleanup
 | ||||||
|         this.git = gitCommandManager; |         this.git = gitCommandManager; | ||||||
|         this.settings = gitSourceSettings || {}; |         this.settings = gitSourceSettings || {}; | ||||||
|         // Token auth header
 |         // Token auth header
 | ||||||
| @@ -187,6 +189,20 @@ class GitAuthHelper { | |||||||
|             yield this.configureToken(); |             yield this.configureToken(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |     getCredentialsConfigPath() { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             if (this.credentialsConfigPath) { | ||||||
|  |                 return this.credentialsConfigPath; | ||||||
|  |             } | ||||||
|  |             const runnerTemp = process.env['RUNNER_TEMP'] || ''; | ||||||
|  |             assert.ok(runnerTemp, 'RUNNER_TEMP is not defined'); | ||||||
|  |             // Create a unique filename for this checkout instance
 | ||||||
|  |             const configFileName = `git-credentials-${(0, uuid_1.v4)()}.config`; | ||||||
|  |             this.credentialsConfigPath = path.join(runnerTemp, configFileName); | ||||||
|  |             core.debug(`Credentials config path: ${this.credentialsConfigPath}`); | ||||||
|  |             return this.credentialsConfigPath; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|     configureTempGlobalConfig() { |     configureTempGlobalConfig() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             var _a; |             var _a; | ||||||
| @@ -229,10 +245,10 @@ class GitAuthHelper { | |||||||
|     configureGlobalAuth() { |     configureGlobalAuth() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             // 'configureTempGlobalConfig' noops if already set, just returns the path
 |             // 'configureTempGlobalConfig' noops if already set, just returns the path
 | ||||||
|             const newGitConfigPath = yield this.configureTempGlobalConfig(); |             yield this.configureTempGlobalConfig(); | ||||||
|             try { |             try { | ||||||
|                 // Configure the token
 |                 // Configure the token
 | ||||||
|                 yield this.configureToken(newGitConfigPath, true); |                 yield this.configureToken(true); | ||||||
|                 // Configure HTTPS instead of SSH
 |                 // Configure HTTPS instead of SSH
 | ||||||
|                 yield this.git.tryConfigUnset(this.insteadOfKey, true); |                 yield this.git.tryConfigUnset(this.insteadOfKey, true); | ||||||
|                 if (!this.settings.sshKey) { |                 if (!this.settings.sshKey) { | ||||||
| @@ -254,11 +270,12 @@ class GitAuthHelper { | |||||||
|             // Remove possible previous HTTPS instead of SSH
 |             // Remove possible previous HTTPS instead of SSH
 | ||||||
|             yield this.removeGitConfig(this.insteadOfKey, true); |             yield this.removeGitConfig(this.insteadOfKey, true); | ||||||
|             if (this.settings.persistCredentials) { |             if (this.settings.persistCredentials) { | ||||||
|  |                 // TODO: UPDATE THIS
 | ||||||
|                 // Configure a placeholder value. This approach avoids the credential being captured
 |                 // Configure a placeholder value. This approach avoids the credential being captured
 | ||||||
|                 // by process creation audit events, which are commonly logged. For more information,
 |                 // by process creation audit events, which are commonly logged. For more information,
 | ||||||
|                 // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
 |                 // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
 | ||||||
|                 const output = yield this.git.submoduleForeach( |                 const output = yield this.git.submoduleForeach( | ||||||
|                 // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
 |                 // Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline
 | ||||||
|                 `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); |                 `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, this.settings.nestedSubmodules); | ||||||
|                 // Replace the placeholder
 |                 // Replace the placeholder
 | ||||||
|                 const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; |                 const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; | ||||||
| @@ -351,20 +368,48 @@ class GitAuthHelper { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     configureToken(configPath, globalConfig) { |     configureToken(globalConfig) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             // Validate args
 |             // Get the credentials config file path in RUNNER_TEMP
 | ||||||
|             assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations'); |             const credentialsConfigPath = yield this.getCredentialsConfigPath(); | ||||||
|             // Default config path
 |             // Write placeholder to the separate credentials config file using git config.
 | ||||||
|             if (!configPath && !globalConfig) { |             // This approach avoids the credential being captured by process creation audit events,
 | ||||||
|                 configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config'); |             // which are commonly logged. For more information, refer to
 | ||||||
|  |             // https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
 | ||||||
|  |             yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, false, false, credentialsConfigPath); | ||||||
|  |             // Replace the placeholder in the credentials config file
 | ||||||
|  |             yield this.replaceTokenPlaceholder(credentialsConfigPath); | ||||||
|  |             // Add include or includeIf to reference the credentials config
 | ||||||
|  |             if (globalConfig) { | ||||||
|  |                 // Global config file is temporary
 | ||||||
|  |                 yield this.git.config('include.path', credentialsConfigPath, true); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 // For local config, use includeIf.gitdir to match the .git directory.
 | ||||||
|  |                 // Configure for both host and container paths to support Docker container actions.
 | ||||||
|  |                 let gitDir = path.join(this.git.getWorkingDirectory(), '.git'); | ||||||
|  |                 console.log(`Git dir: ${gitDir}`); | ||||||
|  |                 core.info(`Git dir: ${gitDir}`); | ||||||
|  |                 // Use forward slashes for git config, even on Windows
 | ||||||
|  |                 gitDir = gitDir.replace(/\\/g, '/'); | ||||||
|  |                 const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`; | ||||||
|  |                 yield this.git.config(hostIncludeKey, credentialsConfigPath); | ||||||
|  |                 this.credentialsIncludeKeys.push(hostIncludeKey); | ||||||
|  |                 // Configure for container scenario where paths are mapped to fixed locations
 | ||||||
|  |                 const githubWorkspace = process.env['GITHUB_WORKSPACE']; | ||||||
|  |                 assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined'); | ||||||
|  |                 // Calculate the relative path of the working directory from GITHUB_WORKSPACE
 | ||||||
|  |                 const workingDirectory = this.git.getWorkingDirectory(); | ||||||
|  |                 let relativePath = path.relative(githubWorkspace, workingDirectory); | ||||||
|  |                 // Container paths: GITHUB_WORKSPACE -> /github/workspace, RUNNER_TEMP -> /github/runner_temp
 | ||||||
|  |                 // Use forward slashes for git config
 | ||||||
|  |                 relativePath = relativePath.replace(/\\/g, '/'); | ||||||
|  |                 const containerGitDir = path.posix.join('/github/workspace', relativePath, '.git'); | ||||||
|  |                 const containerCredentialsPath = path.posix.join('/github/runner_temp', path.basename(credentialsConfigPath)); | ||||||
|  |                 const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path`; | ||||||
|  |                 yield this.git.config(containerIncludeKey, containerCredentialsPath); | ||||||
|  |                 this.credentialsIncludeKeys.push(containerIncludeKey); | ||||||
|             } |             } | ||||||
|             // Configure a placeholder value. This approach avoids the credential being captured
 |  | ||||||
|             // by process creation audit events, which are commonly logged. For more information,
 |  | ||||||
|             // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing
 |  | ||||||
|             yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig); |  | ||||||
|             // Replace the placeholder
 |  | ||||||
|             yield this.replaceTokenPlaceholder(configPath || ''); |  | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     replaceTokenPlaceholder(configPath) { |     replaceTokenPlaceholder(configPath) { | ||||||
| @@ -411,8 +456,24 @@ class GitAuthHelper { | |||||||
|     } |     } | ||||||
|     removeToken() { |     removeToken() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             var _a; | ||||||
|             // HTTP extra header
 |             // HTTP extra header
 | ||||||
|             yield this.removeGitConfig(this.tokenConfigKey); |             yield this.removeGitConfig(this.tokenConfigKey); | ||||||
|  |             // Remove include/includeIf config entries
 | ||||||
|  |             for (const includeKey of this.credentialsIncludeKeys) { | ||||||
|  |                 yield this.removeGitConfig(includeKey); | ||||||
|  |             } | ||||||
|  |             this.credentialsIncludeKeys = []; | ||||||
|  |             // Remove credentials config file
 | ||||||
|  |             if (this.credentialsConfigPath) { | ||||||
|  |                 try { | ||||||
|  |                     yield io.rmRF(this.credentialsConfigPath); | ||||||
|  |                 } | ||||||
|  |                 catch (err) { | ||||||
|  |                     core.debug(`${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}`); | ||||||
|  |                     core.warning(`Failed to remove credentials config '${this.credentialsConfigPath}'`); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     removeGitConfig(configKey_1) { |     removeGitConfig(configKey_1) { | ||||||
| @@ -627,9 +688,15 @@ class GitCommandManager { | |||||||
|             yield this.execGit(args); |             yield this.execGit(args); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     config(configKey, configValue, globalConfig, add) { |     config(configKey, configValue, globalConfig, add, configFile) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             const args = ['config', globalConfig ? '--global' : '--local']; |             const args = ['config']; | ||||||
|  |             if (configFile) { | ||||||
|  |                 args.push('--file', configFile); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 args.push(globalConfig ? '--global' : '--local'); | ||||||
|  |             } | ||||||
|             if (add) { |             if (add) { | ||||||
|                 args.push('--add'); |                 args.push('--add'); | ||||||
|             } |             } | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										20
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "checkout", |   "name": "checkout", | ||||||
|   "version": "4.3.0", |   "version": "5.0.0", | ||||||
|   "lockfileVersion": 3, |   "lockfileVersion": 3, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "checkout", |       "name": "checkout", | ||||||
|       "version": "4.3.0", |       "version": "5.0.0", | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@actions/core": "^1.10.1", |         "@actions/core": "^1.10.1", | ||||||
| @@ -18,7 +18,7 @@ | |||||||
|       }, |       }, | ||||||
|       "devDependencies": { |       "devDependencies": { | ||||||
|         "@types/jest": "^29.5.12", |         "@types/jest": "^29.5.12", | ||||||
|         "@types/node": "^20.12.12", |         "@types/node": "^24.1.0", | ||||||
|         "@types/uuid": "^9.0.8", |         "@types/uuid": "^9.0.8", | ||||||
|         "@typescript-eslint/eslint-plugin": "^7.9.0", |         "@typescript-eslint/eslint-plugin": "^7.9.0", | ||||||
|         "@typescript-eslint/parser": "^7.9.0", |         "@typescript-eslint/parser": "^7.9.0", | ||||||
| @@ -1515,12 +1515,12 @@ | |||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/node": { |     "node_modules/@types/node": { | ||||||
|       "version": "20.12.12", |       "version": "24.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", | ||||||
|       "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", |       "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "undici-types": "~5.26.4" |         "undici-types": "~7.8.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/stack-utils": { |     "node_modules/@types/stack-utils": { | ||||||
| @@ -6865,9 +6865,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/undici-types": { |     "node_modules/undici-types": { | ||||||
|       "version": "5.26.5", |       "version": "7.8.0", | ||||||
|       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", |       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", | ||||||
|       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", |       "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/universal-user-agent": { |     "node_modules/universal-user-agent": { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "checkout", |   "name": "checkout", | ||||||
|   "version": "4.3.0", |   "version": "5.0.0", | ||||||
|   "description": "checkout action", |   "description": "checkout action", | ||||||
|   "main": "lib/main.js", |   "main": "lib/main.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
| @@ -37,7 +37,7 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@types/jest": "^29.5.12", |     "@types/jest": "^29.5.12", | ||||||
|     "@types/node": "^20.12.12", |     "@types/node": "^24.1.0", | ||||||
|     "@types/uuid": "^9.0.8", |     "@types/uuid": "^9.0.8", | ||||||
|     "@typescript-eslint/eslint-plugin": "^7.9.0", |     "@typescript-eslint/eslint-plugin": "^7.9.0", | ||||||
|     "@typescript-eslint/parser": "^7.9.0", |     "@typescript-eslint/parser": "^7.9.0", | ||||||
|   | |||||||
| @@ -43,6 +43,8 @@ class GitAuthHelper { | |||||||
|   private sshKeyPath = '' |   private sshKeyPath = '' | ||||||
|   private sshKnownHostsPath = '' |   private sshKnownHostsPath = '' | ||||||
|   private temporaryHomePath = '' |   private temporaryHomePath = '' | ||||||
|  |   private credentialsConfigPath = '' // Path to separate credentials config file in RUNNER_TEMP | ||||||
|  |   private credentialsIncludeKeys: string[] = [] // Track includeIf/include config keys for cleanup | ||||||
|  |  | ||||||
|   constructor( |   constructor( | ||||||
|     gitCommandManager: IGitCommandManager, |     gitCommandManager: IGitCommandManager, | ||||||
| @@ -81,6 +83,22 @@ class GitAuthHelper { | |||||||
|     await this.configureToken() |     await this.configureToken() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private async getCredentialsConfigPath(): Promise<string> { | ||||||
|  |     if (this.credentialsConfigPath) { | ||||||
|  |       return this.credentialsConfigPath | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const runnerTemp = process.env['RUNNER_TEMP'] || '' | ||||||
|  |     assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') | ||||||
|  |  | ||||||
|  |     // Create a unique filename for this checkout instance | ||||||
|  |     const configFileName = `git-credentials-${uuid()}.config` | ||||||
|  |     this.credentialsConfigPath = path.join(runnerTemp, configFileName) | ||||||
|  |  | ||||||
|  |     core.debug(`Credentials config path: ${this.credentialsConfigPath}`) | ||||||
|  |     return this.credentialsConfigPath | ||||||
|  |   } | ||||||
|  |  | ||||||
|   async configureTempGlobalConfig(): Promise<string> { |   async configureTempGlobalConfig(): Promise<string> { | ||||||
|     // Already setup global config |     // Already setup global config | ||||||
|     if (this.temporaryHomePath?.length > 0) { |     if (this.temporaryHomePath?.length > 0) { | ||||||
| @@ -126,10 +144,10 @@ class GitAuthHelper { | |||||||
|  |  | ||||||
|   async configureGlobalAuth(): Promise<void> { |   async configureGlobalAuth(): Promise<void> { | ||||||
|     // 'configureTempGlobalConfig' noops if already set, just returns the path |     // 'configureTempGlobalConfig' noops if already set, just returns the path | ||||||
|     const newGitConfigPath = await this.configureTempGlobalConfig() |     await this.configureTempGlobalConfig() | ||||||
|     try { |     try { | ||||||
|       // Configure the token |       // Configure the token | ||||||
|       await this.configureToken(newGitConfigPath, true) |       await this.configureToken(true) | ||||||
|  |  | ||||||
|       // Configure HTTPS instead of SSH |       // Configure HTTPS instead of SSH | ||||||
|       await this.git.tryConfigUnset(this.insteadOfKey, true) |       await this.git.tryConfigUnset(this.insteadOfKey, true) | ||||||
| @@ -153,11 +171,13 @@ class GitAuthHelper { | |||||||
|     await this.removeGitConfig(this.insteadOfKey, true) |     await this.removeGitConfig(this.insteadOfKey, true) | ||||||
|  |  | ||||||
|     if (this.settings.persistCredentials) { |     if (this.settings.persistCredentials) { | ||||||
|  |       // TODO: UPDATE THIS | ||||||
|  |  | ||||||
|       // Configure a placeholder value. This approach avoids the credential being captured |       // Configure a placeholder value. This approach avoids the credential being captured | ||||||
|       // by process creation audit events, which are commonly logged. For more information, |       // by process creation audit events, which are commonly logged. For more information, | ||||||
|       // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing |       // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing | ||||||
|       const output = await this.git.submoduleForeach( |       const output = await this.git.submoduleForeach( | ||||||
|         // wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline |         // Wrap the pipeline in quotes to make sure it's handled properly by submoduleForeach, rather than just the first part of the pipeline | ||||||
|         `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, |         `sh -c "git config --local '${this.tokenConfigKey}' '${this.tokenPlaceholderConfigValue}' && git config --local --show-origin --name-only --get-regexp remote.origin.url"`, | ||||||
|         this.settings.nestedSubmodules |         this.settings.nestedSubmodules | ||||||
|       ) |       ) | ||||||
| @@ -272,32 +292,66 @@ class GitAuthHelper { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async configureToken( |   private async configureToken(globalConfig?: boolean): Promise<void> { | ||||||
|     configPath?: string, |     // Get the credentials config file path in RUNNER_TEMP | ||||||
|     globalConfig?: boolean |     const credentialsConfigPath = await this.getCredentialsConfigPath() | ||||||
|   ): Promise<void> { |  | ||||||
|     // Validate args |  | ||||||
|     assert.ok( |  | ||||||
|       (configPath && globalConfig) || (!configPath && !globalConfig), |  | ||||||
|       'Unexpected configureToken parameter combinations' |  | ||||||
|     ) |  | ||||||
|  |  | ||||||
|     // Default config path |     // Write placeholder to the separate credentials config file using git config. | ||||||
|     if (!configPath && !globalConfig) { |     // This approach avoids the credential being captured by process creation audit events, | ||||||
|       configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config') |     // which are commonly logged. For more information, refer to | ||||||
|     } |     // https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing | ||||||
|  |  | ||||||
|     // Configure a placeholder value. This approach avoids the credential being captured |  | ||||||
|     // by process creation audit events, which are commonly logged. For more information, |  | ||||||
|     // refer to https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/command-line-process-auditing |  | ||||||
|     await this.git.config( |     await this.git.config( | ||||||
|       this.tokenConfigKey, |       this.tokenConfigKey, | ||||||
|       this.tokenPlaceholderConfigValue, |       this.tokenPlaceholderConfigValue, | ||||||
|       globalConfig |       false, | ||||||
|  |       false, | ||||||
|  |       credentialsConfigPath | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     // Replace the placeholder |     // Replace the placeholder in the credentials config file | ||||||
|     await this.replaceTokenPlaceholder(configPath || '') |     await this.replaceTokenPlaceholder(credentialsConfigPath) | ||||||
|  |  | ||||||
|  |     // Add include or includeIf to reference the credentials config | ||||||
|  |     if (globalConfig) { | ||||||
|  |       // Global config file is temporary | ||||||
|  |       await this.git.config('include.path', credentialsConfigPath, true) | ||||||
|  |     } else { | ||||||
|  |       // For local config, use includeIf.gitdir to match the .git directory. | ||||||
|  |       // Configure for both host and container paths to support Docker container actions. | ||||||
|  |       let gitDir = path.join(this.git.getWorkingDirectory(), '.git') | ||||||
|  |       console.log(`Git dir: ${gitDir}`) | ||||||
|  |       core.info(`Git dir: ${gitDir}`) | ||||||
|  |       // Use forward slashes for git config, even on Windows | ||||||
|  |       gitDir = gitDir.replace(/\\/g, '/') | ||||||
|  |       const hostIncludeKey = `includeIf.gitdir:${gitDir}.path` | ||||||
|  |       await this.git.config(hostIncludeKey, credentialsConfigPath) | ||||||
|  |       this.credentialsIncludeKeys.push(hostIncludeKey) | ||||||
|  |  | ||||||
|  |       // Configure for container scenario where paths are mapped to fixed locations | ||||||
|  |       const githubWorkspace = process.env['GITHUB_WORKSPACE'] | ||||||
|  |       assert.ok(githubWorkspace, 'GITHUB_WORKSPACE is not defined') | ||||||
|  |        | ||||||
|  |       // Calculate the relative path of the working directory from GITHUB_WORKSPACE | ||||||
|  |       const workingDirectory = this.git.getWorkingDirectory() | ||||||
|  |       let relativePath = path.relative(githubWorkspace, workingDirectory) | ||||||
|  |  | ||||||
|  |       // Container paths: GITHUB_WORKSPACE -> /github/workspace, RUNNER_TEMP -> /github/runner_temp | ||||||
|  |       // Use forward slashes for git config | ||||||
|  |       relativePath = relativePath.replace(/\\/g, '/') | ||||||
|  |       const containerGitDir = path.posix.join( | ||||||
|  |         '/github/workspace', | ||||||
|  |         relativePath, | ||||||
|  |         '.git' | ||||||
|  |       ) | ||||||
|  |       const containerCredentialsPath = path.posix.join( | ||||||
|  |         '/github/runner_temp', | ||||||
|  |         path.basename(credentialsConfigPath) | ||||||
|  |       ) | ||||||
|  |  | ||||||
|  |       const containerIncludeKey = `includeIf.gitdir:${containerGitDir}.path` | ||||||
|  |       await this.git.config(containerIncludeKey, containerCredentialsPath) | ||||||
|  |       this.credentialsIncludeKeys.push(containerIncludeKey) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async replaceTokenPlaceholder(configPath: string): Promise<void> { |   private async replaceTokenPlaceholder(configPath: string): Promise<void> { | ||||||
| @@ -348,6 +402,24 @@ class GitAuthHelper { | |||||||
|   private async removeToken(): Promise<void> { |   private async removeToken(): Promise<void> { | ||||||
|     // HTTP extra header |     // HTTP extra header | ||||||
|     await this.removeGitConfig(this.tokenConfigKey) |     await this.removeGitConfig(this.tokenConfigKey) | ||||||
|  |  | ||||||
|  |     // Remove include/includeIf config entries | ||||||
|  |     for (const includeKey of this.credentialsIncludeKeys) { | ||||||
|  |       await this.removeGitConfig(includeKey) | ||||||
|  |     } | ||||||
|  |     this.credentialsIncludeKeys = [] | ||||||
|  |  | ||||||
|  |     // Remove credentials config file | ||||||
|  |     if (this.credentialsConfigPath) { | ||||||
|  |       try { | ||||||
|  |         await io.rmRF(this.credentialsConfigPath) | ||||||
|  |       } catch (err) { | ||||||
|  |         core.debug(`${(err as any)?.message ?? err}`) | ||||||
|  |         core.warning( | ||||||
|  |           `Failed to remove credentials config '${this.credentialsConfigPath}'` | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async removeGitConfig( |   private async removeGitConfig( | ||||||
|   | |||||||
| @@ -28,7 +28,8 @@ export interface IGitCommandManager { | |||||||
|     configKey: string, |     configKey: string, | ||||||
|     configValue: string, |     configValue: string, | ||||||
|     globalConfig?: boolean, |     globalConfig?: boolean, | ||||||
|     add?: boolean |     add?: boolean, | ||||||
|  |     configFile?: string | ||||||
|   ): Promise<void> |   ): Promise<void> | ||||||
|   configExists(configKey: string, globalConfig?: boolean): Promise<boolean> |   configExists(configKey: string, globalConfig?: boolean): Promise<boolean> | ||||||
|   fetch( |   fetch( | ||||||
| @@ -223,9 +224,15 @@ class GitCommandManager { | |||||||
|     configKey: string, |     configKey: string, | ||||||
|     configValue: string, |     configValue: string, | ||||||
|     globalConfig?: boolean, |     globalConfig?: boolean, | ||||||
|     add?: boolean |     add?: boolean, | ||||||
|  |     configFile?: string | ||||||
|   ): Promise<void> { |   ): Promise<void> { | ||||||
|     const args: string[] = ['config', globalConfig ? '--global' : '--local'] |     const args: string[] = ['config'] | ||||||
|  |     if (configFile) { | ||||||
|  |       args.push('--file', configFile) | ||||||
|  |     } else { | ||||||
|  |       args.push(globalConfig ? '--global' : '--local') | ||||||
|  |     } | ||||||
|     if (add) { |     if (add) { | ||||||
|       args.push('--add') |       args.push('--add') | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -120,7 +120,7 @@ function updateUsage( | |||||||
| } | } | ||||||
|  |  | ||||||
| updateUsage( | updateUsage( | ||||||
|   'actions/checkout@v4', |   'actions/checkout@v5', | ||||||
|   path.join(__dirname, '..', '..', 'action.yml'), |   path.join(__dirname, '..', '..', 'action.yml'), | ||||||
|   path.join(__dirname, '..', '..', 'README.md') |   path.join(__dirname, '..', '..', 'README.md') | ||||||
| ) | ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user