mirror of
				https://github.com/actions/checkout.git
				synced 2025-10-31 03:03:36 +00:00 
			
		
		
		
	add support for submodules (#173)
This commit is contained in:
		
							parent
							
								
									204620207c
								
							
						
					
					
						commit
						422dc45671
					
				
							
								
								
									
										29
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @ -84,6 +84,35 @@ jobs: | |||||||
|         shell: bash |         shell: bash | ||||||
|         run: __test__/verify-lfs.sh |         run: __test__/verify-lfs.sh | ||||||
| 
 | 
 | ||||||
|  |       # Submodules false | ||||||
|  |       - name: Submodules false checkout | ||||||
|  |         uses: ./ | ||||||
|  |         with: | ||||||
|  |           ref: test-data/v2/submodule | ||||||
|  |           path: submodules-false | ||||||
|  |       - name: Verify submodules false | ||||||
|  |         run: __test__/verify-submodules-false.sh | ||||||
|  | 
 | ||||||
|  |       # Submodules one level | ||||||
|  |       - name: Submodules true checkout | ||||||
|  |         uses: ./ | ||||||
|  |         with: | ||||||
|  |           ref: test-data/v2/submodule | ||||||
|  |           path: submodules-true | ||||||
|  |           submodules: true | ||||||
|  |       - name: Verify submodules true | ||||||
|  |         run: __test__/verify-submodules-true.sh | ||||||
|  | 
 | ||||||
|  |       # Submodules recursive | ||||||
|  |       - name: Submodules recursive checkout | ||||||
|  |         uses: ./ | ||||||
|  |         with: | ||||||
|  |           ref: test-data/v2/submodule | ||||||
|  |           path: submodules-recursive | ||||||
|  |           submodules: recursive | ||||||
|  |       - name: Verify submodules recursive | ||||||
|  |         run: __test__/verify-submodules-recursive.sh | ||||||
|  | 
 | ||||||
|       # Basic checkout using REST API |       # Basic checkout using REST API | ||||||
|       - name: Remove basic |       - name: Remove basic | ||||||
|         if: runner.os != 'windows' |         if: runner.os != 'windows' | ||||||
|  | |||||||
| @ -70,6 +70,11 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous | |||||||
|     # Whether to download Git-LFS files |     # Whether to download Git-LFS files | ||||||
|     # Default: false |     # Default: false | ||||||
|     lfs: '' |     lfs: '' | ||||||
|  | 
 | ||||||
|  |     # Whether to checkout submodules: `true` to checkout submodules or `recursive` to | ||||||
|  |     # recursively checkout submodules. | ||||||
|  |     # Default: false | ||||||
|  |     submodules: '' | ||||||
| ``` | ``` | ||||||
| <!-- end usage --> | <!-- end usage --> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,10 +8,13 @@ import {IGitSourceSettings} from '../lib/git-source-settings' | |||||||
| 
 | 
 | ||||||
| const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper') | const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper') | ||||||
| const originalRunnerTemp = process.env['RUNNER_TEMP'] | const originalRunnerTemp = process.env['RUNNER_TEMP'] | ||||||
|  | const originalHome = process.env['HOME'] | ||||||
| let workspace: string | let workspace: string | ||||||
| let gitConfigPath: string | let localGitConfigPath: string | ||||||
|  | let globalGitConfigPath: string | ||||||
| let runnerTemp: string | let runnerTemp: string | ||||||
| let git: IGitCommandManager | let tempHomedir: string | ||||||
|  | let git: IGitCommandManager & {env: {[key: string]: string}} | ||||||
| let settings: IGitSourceSettings | let settings: IGitSourceSettings | ||||||
| 
 | 
 | ||||||
| describe('git-auth-helper tests', () => { | describe('git-auth-helper tests', () => { | ||||||
| @ -23,11 +26,24 @@ describe('git-auth-helper tests', () => { | |||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     // Mock setSecret
 |     // Mock setSecret
 | ||||||
|     jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {}) |     jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {}) | ||||||
|  | 
 | ||||||
|  |     // Mock error/warning/info/debug
 | ||||||
|  |     jest.spyOn(core, 'error').mockImplementation(jest.fn()) | ||||||
|  |     jest.spyOn(core, 'warning').mockImplementation(jest.fn()) | ||||||
|  |     jest.spyOn(core, 'info').mockImplementation(jest.fn()) | ||||||
|  |     jest.spyOn(core, 'debug').mockImplementation(jest.fn()) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   afterEach(() => { |   afterEach(() => { | ||||||
|     // Unregister mocks
 |     // Unregister mocks
 | ||||||
|     jest.restoreAllMocks() |     jest.restoreAllMocks() | ||||||
|  | 
 | ||||||
|  |     // Restore HOME
 | ||||||
|  |     if (originalHome) { | ||||||
|  |       process.env['HOME'] = originalHome | ||||||
|  |     } else { | ||||||
|  |       delete process.env['HOME'] | ||||||
|  |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   afterAll(() => { |   afterAll(() => { | ||||||
| @ -38,10 +54,11 @@ describe('git-auth-helper tests', () => { | |||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const configuresAuthHeader = 'configures auth header' |   const configureAuth_configuresAuthHeader = | ||||||
|   it(configuresAuthHeader, async () => { |     'configureAuth configures auth header' | ||||||
|  |   it(configureAuth_configuresAuthHeader, async () => { | ||||||
|     // Arrange
 |     // Arrange
 | ||||||
|     await setup(configuresAuthHeader) |     await setup(configureAuth_configuresAuthHeader) | ||||||
|     expect(settings.authToken).toBeTruthy() // sanity check
 |     expect(settings.authToken).toBeTruthy() // sanity check
 | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
| 
 | 
 | ||||||
| @ -49,7 +66,9 @@ describe('git-auth-helper tests', () => { | |||||||
|     await authHelper.configureAuth() |     await authHelper.configureAuth() | ||||||
| 
 | 
 | ||||||
|     // Assert config
 |     // Assert config
 | ||||||
|     const configContent = (await fs.promises.readFile(gitConfigPath)).toString() |     const configContent = ( | ||||||
|  |       await fs.promises.readFile(localGitConfigPath) | ||||||
|  |     ).toString() | ||||||
|     const basicCredential = Buffer.from( |     const basicCredential = Buffer.from( | ||||||
|       `x-access-token:${settings.authToken}`, |       `x-access-token:${settings.authToken}`, | ||||||
|       'utf8' |       'utf8' | ||||||
| @ -61,32 +80,39 @@ describe('git-auth-helper tests', () => { | |||||||
|     ).toBeGreaterThanOrEqual(0) |     ).toBeGreaterThanOrEqual(0) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const configuresAuthHeaderEvenWhenPersistCredentialsFalse = |   const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse = | ||||||
|     'configures auth header even when persist credentials false' |     'configureAuth configures auth header even when persist credentials false' | ||||||
|   it(configuresAuthHeaderEvenWhenPersistCredentialsFalse, async () => { |   it( | ||||||
|     // Arrange
 |     configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse, | ||||||
|     await setup(configuresAuthHeaderEvenWhenPersistCredentialsFalse) |     async () => { | ||||||
|     expect(settings.authToken).toBeTruthy() // sanity check
 |       // Arrange
 | ||||||
|     settings.persistCredentials = false |       await setup( | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |         configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse | ||||||
| 
 |  | ||||||
|     // Act
 |  | ||||||
|     await authHelper.configureAuth() |  | ||||||
| 
 |  | ||||||
|     // Assert config
 |  | ||||||
|     const configContent = (await fs.promises.readFile(gitConfigPath)).toString() |  | ||||||
|     expect( |  | ||||||
|       configContent.indexOf( |  | ||||||
|         `http.https://github.com/.extraheader AUTHORIZATION` |  | ||||||
|       ) |       ) | ||||||
|     ).toBeGreaterThanOrEqual(0) |       expect(settings.authToken).toBeTruthy() // sanity check
 | ||||||
|   }) |       settings.persistCredentials = false | ||||||
|  |       const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
| 
 | 
 | ||||||
|   const registersBasicCredentialAsSecret = |       // Act
 | ||||||
|     'registers basic credential as secret' |       await authHelper.configureAuth() | ||||||
|   it(registersBasicCredentialAsSecret, async () => { | 
 | ||||||
|  |       // Assert config
 | ||||||
|  |       const configContent = ( | ||||||
|  |         await fs.promises.readFile(localGitConfigPath) | ||||||
|  |       ).toString() | ||||||
|  |       expect( | ||||||
|  |         configContent.indexOf( | ||||||
|  |           `http.https://github.com/.extraheader AUTHORIZATION` | ||||||
|  |         ) | ||||||
|  |       ).toBeGreaterThanOrEqual(0) | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   const configureAuth_registersBasicCredentialAsSecret = | ||||||
|  |     'configureAuth registers basic credential as secret' | ||||||
|  |   it(configureAuth_registersBasicCredentialAsSecret, async () => { | ||||||
|     // Arrange
 |     // Arrange
 | ||||||
|     await setup(registersBasicCredentialAsSecret) |     await setup(configureAuth_registersBasicCredentialAsSecret) | ||||||
|     expect(settings.authToken).toBeTruthy() // sanity check
 |     expect(settings.authToken).toBeTruthy() // sanity check
 | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
| 
 | 
 | ||||||
| @ -103,14 +129,139 @@ describe('git-auth-helper tests', () => { | |||||||
|     expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret) |     expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret) | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   const removesToken = 'removes token' |   const configureGlobalAuth_copiesGlobalGitConfig = | ||||||
|   it(removesToken, async () => { |     'configureGlobalAuth copies global git config' | ||||||
|  |   it(configureGlobalAuth_copiesGlobalGitConfig, async () => { | ||||||
|     // Arrange
 |     // Arrange
 | ||||||
|     await setup(removesToken) |     await setup(configureGlobalAuth_copiesGlobalGitConfig) | ||||||
|  |     await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config') | ||||||
|  |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  | 
 | ||||||
|  |     // Act
 | ||||||
|  |     await authHelper.configureAuth() | ||||||
|  |     await authHelper.configureGlobalAuth() | ||||||
|  | 
 | ||||||
|  |     // Assert original global config not altered
 | ||||||
|  |     let configContent = ( | ||||||
|  |       await fs.promises.readFile(globalGitConfigPath) | ||||||
|  |     ).toString() | ||||||
|  |     expect(configContent).toBe('value-from-global-config') | ||||||
|  | 
 | ||||||
|  |     // Assert temporary global config
 | ||||||
|  |     expect(git.env['HOME']).toBeTruthy() | ||||||
|  |     const basicCredential = Buffer.from( | ||||||
|  |       `x-access-token:${settings.authToken}`, | ||||||
|  |       'utf8' | ||||||
|  |     ).toString('base64') | ||||||
|  |     configContent = ( | ||||||
|  |       await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) | ||||||
|  |     ).toString() | ||||||
|  |     expect( | ||||||
|  |       configContent.indexOf('value-from-global-config') | ||||||
|  |     ).toBeGreaterThanOrEqual(0) | ||||||
|  |     expect( | ||||||
|  |       configContent.indexOf( | ||||||
|  |         `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` | ||||||
|  |       ) | ||||||
|  |     ).toBeGreaterThanOrEqual(0) | ||||||
|  |   }) | ||||||
|  | 
 | ||||||
|  |   const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist = | ||||||
|  |     'configureGlobalAuth creates new git config when global does not exist' | ||||||
|  |   it( | ||||||
|  |     configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist, | ||||||
|  |     async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       await setup( | ||||||
|  |         configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist | ||||||
|  |       ) | ||||||
|  |       await io.rmRF(globalGitConfigPath) | ||||||
|  |       const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await authHelper.configureAuth() | ||||||
|  |       await authHelper.configureGlobalAuth() | ||||||
|  | 
 | ||||||
|  |       // Assert original global config not recreated
 | ||||||
|  |       try { | ||||||
|  |         await fs.promises.stat(globalGitConfigPath) | ||||||
|  |         throw new Error( | ||||||
|  |           `Did not expect file to exist: '${globalGitConfigPath}'` | ||||||
|  |         ) | ||||||
|  |       } catch (err) { | ||||||
|  |         if (err.code !== 'ENOENT') { | ||||||
|  |           throw err | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Assert temporary global config
 | ||||||
|  |       expect(git.env['HOME']).toBeTruthy() | ||||||
|  |       const basicCredential = Buffer.from( | ||||||
|  |         `x-access-token:${settings.authToken}`, | ||||||
|  |         'utf8' | ||||||
|  |       ).toString('base64') | ||||||
|  |       const configContent = ( | ||||||
|  |         await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) | ||||||
|  |       ).toString() | ||||||
|  |       expect( | ||||||
|  |         configContent.indexOf( | ||||||
|  |           `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` | ||||||
|  |         ) | ||||||
|  |       ).toBeGreaterThanOrEqual(0) | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   const configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse = | ||||||
|  |     'configureSubmoduleAuth does not configure token when persist credentials false' | ||||||
|  |   it( | ||||||
|  |     configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse, | ||||||
|  |     async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       await setup( | ||||||
|  |         configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse | ||||||
|  |       ) | ||||||
|  |       settings.persistCredentials = false | ||||||
|  |       const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  |       await authHelper.configureAuth() | ||||||
|  |       ;(git.submoduleForeach as jest.Mock<any, any>).mockClear() // reset calls
 | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await authHelper.configureSubmoduleAuth() | ||||||
|  | 
 | ||||||
|  |       // Assert
 | ||||||
|  |       expect(git.submoduleForeach).not.toHaveBeenCalled() | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue = | ||||||
|  |     'configureSubmoduleAuth configures token when persist credentials true' | ||||||
|  |   it( | ||||||
|  |     configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue, | ||||||
|  |     async () => { | ||||||
|  |       // Arrange
 | ||||||
|  |       await setup( | ||||||
|  |         configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue | ||||||
|  |       ) | ||||||
|  |       const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  |       await authHelper.configureAuth() | ||||||
|  |       ;(git.submoduleForeach as jest.Mock<any, any>).mockClear() // reset calls
 | ||||||
|  | 
 | ||||||
|  |       // Act
 | ||||||
|  |       await authHelper.configureSubmoduleAuth() | ||||||
|  | 
 | ||||||
|  |       // Assert
 | ||||||
|  |       expect(git.submoduleForeach).toHaveBeenCalledTimes(1) | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  | 
 | ||||||
|  |   const removeAuth_removesToken = 'removeAuth removes token' | ||||||
|  |   it(removeAuth_removesToken, async () => { | ||||||
|  |     // Arrange
 | ||||||
|  |     await setup(removeAuth_removesToken) | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|     await authHelper.configureAuth() |     await authHelper.configureAuth() | ||||||
|     let gitConfigContent = ( |     let gitConfigContent = ( | ||||||
|       await fs.promises.readFile(gitConfigPath) |       await fs.promises.readFile(localGitConfigPath) | ||||||
|     ).toString() |     ).toString() | ||||||
|     expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
 |     expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
 | ||||||
| 
 | 
 | ||||||
| @ -118,9 +269,37 @@ describe('git-auth-helper tests', () => { | |||||||
|     await authHelper.removeAuth() |     await authHelper.removeAuth() | ||||||
| 
 | 
 | ||||||
|     // Assert git config
 |     // Assert git config
 | ||||||
|     gitConfigContent = (await fs.promises.readFile(gitConfigPath)).toString() |     gitConfigContent = ( | ||||||
|  |       await fs.promises.readFile(localGitConfigPath) | ||||||
|  |     ).toString() | ||||||
|     expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) |     expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) | ||||||
|   }) |   }) | ||||||
|  | 
 | ||||||
|  |   const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override' | ||||||
|  |   it(removeGlobalAuth_removesOverride, async () => { | ||||||
|  |     // Arrange
 | ||||||
|  |     await setup(removeGlobalAuth_removesOverride) | ||||||
|  |     const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  |     await authHelper.configureAuth() | ||||||
|  |     await authHelper.configureGlobalAuth() | ||||||
|  |     const homeOverride = git.env['HOME'] // Sanity check
 | ||||||
|  |     expect(homeOverride).toBeTruthy() | ||||||
|  |     await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) | ||||||
|  | 
 | ||||||
|  |     // Act
 | ||||||
|  |     await authHelper.removeGlobalAuth() | ||||||
|  | 
 | ||||||
|  |     // Assert
 | ||||||
|  |     expect(git.env['HOME']).toBeUndefined() | ||||||
|  |     try { | ||||||
|  |       await fs.promises.stat(homeOverride) | ||||||
|  |       throw new Error(`Should have been deleted '${homeOverride}'`) | ||||||
|  |     } catch (err) { | ||||||
|  |       if (err.code !== 'ENOENT') { | ||||||
|  |         throw err | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }) | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| async function setup(testName: string): Promise<void> { | async function setup(testName: string): Promise<void> { | ||||||
| @ -129,14 +308,19 @@ async function setup(testName: string): Promise<void> { | |||||||
|   // Directories
 |   // Directories
 | ||||||
|   workspace = path.join(testWorkspace, testName, 'workspace') |   workspace = path.join(testWorkspace, testName, 'workspace') | ||||||
|   runnerTemp = path.join(testWorkspace, testName, 'runner-temp') |   runnerTemp = path.join(testWorkspace, testName, 'runner-temp') | ||||||
|  |   tempHomedir = path.join(testWorkspace, testName, 'home-dir') | ||||||
|   await fs.promises.mkdir(workspace, {recursive: true}) |   await fs.promises.mkdir(workspace, {recursive: true}) | ||||||
|   await fs.promises.mkdir(runnerTemp, {recursive: true}) |   await fs.promises.mkdir(runnerTemp, {recursive: true}) | ||||||
|  |   await fs.promises.mkdir(tempHomedir, {recursive: true}) | ||||||
|   process.env['RUNNER_TEMP'] = runnerTemp |   process.env['RUNNER_TEMP'] = runnerTemp | ||||||
|  |   process.env['HOME'] = tempHomedir | ||||||
| 
 | 
 | ||||||
|   // Create git config
 |   // Create git config
 | ||||||
|   gitConfigPath = path.join(workspace, '.git', 'config') |   globalGitConfigPath = path.join(tempHomedir, '.gitconfig') | ||||||
|   await fs.promises.mkdir(path.join(workspace, '.git'), {recursive: true}) |   await fs.promises.writeFile(globalGitConfigPath, '') | ||||||
|   await fs.promises.writeFile(path.join(workspace, '.git', 'config'), '') |   localGitConfigPath = path.join(workspace, '.git', 'config') | ||||||
|  |   await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true}) | ||||||
|  |   await fs.promises.writeFile(localGitConfigPath, '') | ||||||
| 
 | 
 | ||||||
|   git = { |   git = { | ||||||
|     branchDelete: jest.fn(), |     branchDelete: jest.fn(), | ||||||
| @ -144,12 +328,20 @@ async function setup(testName: string): Promise<void> { | |||||||
|     branchList: jest.fn(), |     branchList: jest.fn(), | ||||||
|     checkout: jest.fn(), |     checkout: jest.fn(), | ||||||
|     checkoutDetach: jest.fn(), |     checkoutDetach: jest.fn(), | ||||||
|     config: jest.fn(async (key: string, value: string) => { |     config: jest.fn( | ||||||
|       await fs.promises.appendFile(gitConfigPath, `\n${key} ${value}`) |       async (key: string, value: string, globalConfig?: boolean) => { | ||||||
|     }), |         const configPath = globalConfig | ||||||
|  |           ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') | ||||||
|  |           : localGitConfigPath | ||||||
|  |         await fs.promises.appendFile(configPath, `\n${key} ${value}`) | ||||||
|  |       } | ||||||
|  |     ), | ||||||
|     configExists: jest.fn( |     configExists: jest.fn( | ||||||
|       async (key: string): Promise<boolean> => { |       async (key: string, globalConfig?: boolean): Promise<boolean> => { | ||||||
|         const content = await fs.promises.readFile(gitConfigPath) |         const configPath = globalConfig | ||||||
|  |           ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') | ||||||
|  |           : localGitConfigPath | ||||||
|  |         const content = await fs.promises.readFile(configPath) | ||||||
|         const lines = content |         const lines = content | ||||||
|           .toString() |           .toString() | ||||||
|           .split('\n') |           .split('\n') | ||||||
| @ -157,6 +349,7 @@ async function setup(testName: string): Promise<void> { | |||||||
|         return lines.some(x => x.startsWith(key)) |         return lines.some(x => x.startsWith(key)) | ||||||
|       } |       } | ||||||
|     ), |     ), | ||||||
|  |     env: {}, | ||||||
|     fetch: jest.fn(), |     fetch: jest.fn(), | ||||||
|     getWorkingDirectory: jest.fn(() => workspace), |     getWorkingDirectory: jest.fn(() => workspace), | ||||||
|     init: jest.fn(), |     init: jest.fn(), | ||||||
| @ -165,18 +358,29 @@ async function setup(testName: string): Promise<void> { | |||||||
|     lfsInstall: jest.fn(), |     lfsInstall: jest.fn(), | ||||||
|     log1: jest.fn(), |     log1: jest.fn(), | ||||||
|     remoteAdd: jest.fn(), |     remoteAdd: jest.fn(), | ||||||
|     setEnvironmentVariable: jest.fn(), |     removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]), | ||||||
|  |     setEnvironmentVariable: jest.fn((name: string, value: string) => { | ||||||
|  |       git.env[name] = value | ||||||
|  |     }), | ||||||
|  |     submoduleForeach: jest.fn(async () => { | ||||||
|  |       return '' | ||||||
|  |     }), | ||||||
|  |     submoduleSync: jest.fn(), | ||||||
|  |     submoduleUpdate: jest.fn(), | ||||||
|     tagExists: jest.fn(), |     tagExists: jest.fn(), | ||||||
|     tryClean: jest.fn(), |     tryClean: jest.fn(), | ||||||
|     tryConfigUnset: jest.fn( |     tryConfigUnset: jest.fn( | ||||||
|       async (key: string): Promise<boolean> => { |       async (key: string, globalConfig?: boolean): Promise<boolean> => { | ||||||
|         let content = await fs.promises.readFile(gitConfigPath) |         const configPath = globalConfig | ||||||
|  |           ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') | ||||||
|  |           : localGitConfigPath | ||||||
|  |         let content = await fs.promises.readFile(configPath) | ||||||
|         let lines = content |         let lines = content | ||||||
|           .toString() |           .toString() | ||||||
|           .split('\n') |           .split('\n') | ||||||
|           .filter(x => x) |           .filter(x => x) | ||||||
|           .filter(x => !x.startsWith(key)) |           .filter(x => !x.startsWith(key)) | ||||||
|         await fs.promises.writeFile(gitConfigPath, lines.join('\n')) |         await fs.promises.writeFile(configPath, lines.join('\n')) | ||||||
|         return true |         return true | ||||||
|       } |       } | ||||||
|     ), |     ), | ||||||
| @ -191,6 +395,8 @@ async function setup(testName: string): Promise<void> { | |||||||
|     commit: '', |     commit: '', | ||||||
|     fetchDepth: 1, |     fetchDepth: 1, | ||||||
|     lfs: false, |     lfs: false, | ||||||
|  |     submodules: false, | ||||||
|  |     nestedSubmodules: false, | ||||||
|     persistCredentials: true, |     persistCredentials: true, | ||||||
|     ref: 'refs/heads/master', |     ref: 'refs/heads/master', | ||||||
|     repositoryName: 'my-repo', |     repositoryName: 'my-repo', | ||||||
|  | |||||||
| @ -363,7 +363,11 @@ async function setup(testName: string): Promise<void> { | |||||||
|     lfsInstall: jest.fn(), |     lfsInstall: jest.fn(), | ||||||
|     log1: jest.fn(), |     log1: jest.fn(), | ||||||
|     remoteAdd: jest.fn(), |     remoteAdd: jest.fn(), | ||||||
|  |     removeEnvironmentVariable: jest.fn(), | ||||||
|     setEnvironmentVariable: jest.fn(), |     setEnvironmentVariable: jest.fn(), | ||||||
|  |     submoduleForeach: jest.fn(), | ||||||
|  |     submoduleSync: jest.fn(), | ||||||
|  |     submoduleUpdate: jest.fn(), | ||||||
|     tagExists: jest.fn(), |     tagExists: jest.fn(), | ||||||
|     tryClean: jest.fn(async () => { |     tryClean: jest.fn(async () => { | ||||||
|       return true |       return true | ||||||
|  | |||||||
| @ -130,11 +130,4 @@ describe('input-helper tests', () => { | |||||||
|     expect(settings.ref).toBe('refs/heads/some-other-ref') |     expect(settings.ref).toBe('refs/heads/some-other-ref') | ||||||
|     expect(settings.commit).toBeFalsy() |     expect(settings.commit).toBeFalsy() | ||||||
|   }) |   }) | ||||||
| 
 |  | ||||||
|   it('gives good error message for submodules input', () => { |  | ||||||
|     inputs.submodules = 'true' |  | ||||||
|     assert.throws(() => { |  | ||||||
|       inputHelper.getInputs() |  | ||||||
|     }, /The input 'submodules' is not supported/) |  | ||||||
|   }) |  | ||||||
| }) | }) | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								__test__/verify-submodules-false.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								__test__/verify-submodules-false.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-false/regular-file.txt" ]; then | ||||||
|  |     echo "Expected regular file does not exist" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -f "./submodules-false/submodule-level-1/submodule-file.txt" ]; then | ||||||
|  |     echo "Unexpected submodule file exists" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
| @ -1,11 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| if [ ! -f "./submodules-not-checked-out/regular-file.txt" ]; then |  | ||||||
|     echo "Expected regular file does not exist" |  | ||||||
|     exit 1 |  | ||||||
| fi |  | ||||||
| 
 |  | ||||||
| if [ -f "./submodules-not-checked-out/submodule-level-1/submodule-file.txt" ]; then |  | ||||||
|     echo "Unexpected submodule file exists" |  | ||||||
|     exit 1 |  | ||||||
| fi |  | ||||||
							
								
								
									
										26
									
								
								__test__/verify-submodules-recursive.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								__test__/verify-submodules-recursive.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-recursive/regular-file.txt" ]; then | ||||||
|  |     echo "Expected regular file does not exist" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-recursive/submodule-level-1/submodule-file.txt" ]; then | ||||||
|  |     echo "Expected submodule file does not exist" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-recursive/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then | ||||||
|  |     echo "Expected nested submodule file does not exists" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | echo "Testing persisted credential" | ||||||
|  | pushd ./submodules-recursive/submodule-level-1/submodule-level-2 | ||||||
|  | git config --local --name-only --get-regexp http.+extraheader && git fetch | ||||||
|  | if [ "$?" != "0" ]; then | ||||||
|  |     echo "Failed to validate persisted credential" | ||||||
|  |     popd | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | popd | ||||||
							
								
								
									
										26
									
								
								__test__/verify-submodules-true.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										26
									
								
								__test__/verify-submodules-true.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-true/regular-file.txt" ]; then | ||||||
|  |     echo "Expected regular file does not exist" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ ! -f "./submodules-true/submodule-level-1/submodule-file.txt" ]; then | ||||||
|  |     echo "Expected submodule file does not exist" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -f "./submodules-true/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then | ||||||
|  |     echo "Unexpected nested submodule file exists" | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | echo "Testing persisted credential" | ||||||
|  | pushd ./submodules-true/submodule-level-1 | ||||||
|  | git config --local --name-only --get-regexp http.+extraheader && git fetch | ||||||
|  | if [ "$?" != "0" ]; then | ||||||
|  |     echo "Failed to validate persisted credential" | ||||||
|  |     popd | ||||||
|  |     exit 1 | ||||||
|  | fi | ||||||
|  | popd | ||||||
| @ -30,6 +30,11 @@ inputs: | |||||||
|   lfs: |   lfs: | ||||||
|     description: 'Whether to download Git-LFS files' |     description: 'Whether to download Git-LFS files' | ||||||
|     default: false |     default: false | ||||||
|  |   submodules: | ||||||
|  |     description: > | ||||||
|  |       Whether to checkout submodules: `true` to checkout submodules or `recursive` to | ||||||
|  |       recursively checkout submodules. | ||||||
|  |     default: false | ||||||
| runs: | runs: | ||||||
|   using: node12 |   using: node12 | ||||||
|   main: dist/index.js |   main: dist/index.js | ||||||
|  | |||||||
							
								
								
									
										309
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										309
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @ -5074,21 +5074,35 @@ var __importStar = (this && this.__importStar) || function (mod) { | |||||||
|     result["default"] = mod; |     result["default"] = mod; | ||||||
|     return result; |     return result; | ||||||
| }; | }; | ||||||
|  | var __importDefault = (this && this.__importDefault) || function (mod) { | ||||||
|  |     return (mod && mod.__esModule) ? mod : { "default": mod }; | ||||||
|  | }; | ||||||
| Object.defineProperty(exports, "__esModule", { value: true }); | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | const assert = __importStar(__webpack_require__(357)); | ||||||
| const core = __importStar(__webpack_require__(470)); | const core = __importStar(__webpack_require__(470)); | ||||||
| const fs = __importStar(__webpack_require__(747)); | const fs = __importStar(__webpack_require__(747)); | ||||||
|  | const io = __importStar(__webpack_require__(1)); | ||||||
|  | const os = __importStar(__webpack_require__(87)); | ||||||
| const path = __importStar(__webpack_require__(622)); | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const regexpHelper = __importStar(__webpack_require__(528)); | ||||||
|  | const v4_1 = __importDefault(__webpack_require__(826)); | ||||||
| const IS_WINDOWS = process.platform === 'win32'; | const IS_WINDOWS = process.platform === 'win32'; | ||||||
| const HOSTNAME = 'github.com'; | const HOSTNAME = 'github.com'; | ||||||
| const EXTRA_HEADER_KEY = `http.https://${HOSTNAME}/.extraheader`; |  | ||||||
| function createAuthHelper(git, settings) { | function createAuthHelper(git, settings) { | ||||||
|     return new GitAuthHelper(git, settings); |     return new GitAuthHelper(git, settings); | ||||||
| } | } | ||||||
| exports.createAuthHelper = createAuthHelper; | exports.createAuthHelper = createAuthHelper; | ||||||
| class GitAuthHelper { | class GitAuthHelper { | ||||||
|     constructor(gitCommandManager, gitSourceSettings) { |     constructor(gitCommandManager, gitSourceSettings) { | ||||||
|  |         this.tokenConfigKey = `http.https://${HOSTNAME}/.extraheader`; | ||||||
|  |         this.temporaryHomePath = ''; | ||||||
|         this.git = gitCommandManager; |         this.git = gitCommandManager; | ||||||
|         this.settings = gitSourceSettings || {}; |         this.settings = gitSourceSettings || {}; | ||||||
|  |         // Token auth header
 | ||||||
|  |         const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64'); | ||||||
|  |         core.setSecret(basicCredential); | ||||||
|  |         this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***`; | ||||||
|  |         this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}`; | ||||||
|     } |     } | ||||||
|     configureAuth() { |     configureAuth() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
| @ -5098,37 +5112,110 @@ class GitAuthHelper { | |||||||
|             yield this.configureToken(); |             yield this.configureToken(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |     configureGlobalAuth() { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             // Create a temp home directory
 | ||||||
|  |             const runnerTemp = process.env['RUNNER_TEMP'] || ''; | ||||||
|  |             assert.ok(runnerTemp, 'RUNNER_TEMP is not defined'); | ||||||
|  |             const uniqueId = v4_1.default(); | ||||||
|  |             this.temporaryHomePath = path.join(runnerTemp, uniqueId); | ||||||
|  |             yield fs.promises.mkdir(this.temporaryHomePath, { recursive: true }); | ||||||
|  |             // Copy the global git config
 | ||||||
|  |             const gitConfigPath = path.join(process.env['HOME'] || os.homedir(), '.gitconfig'); | ||||||
|  |             const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig'); | ||||||
|  |             let configExists = false; | ||||||
|  |             try { | ||||||
|  |                 yield fs.promises.stat(gitConfigPath); | ||||||
|  |                 configExists = true; | ||||||
|  |             } | ||||||
|  |             catch (err) { | ||||||
|  |                 if (err.code !== 'ENOENT') { | ||||||
|  |                     throw err; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (configExists) { | ||||||
|  |                 core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`); | ||||||
|  |                 yield io.cp(gitConfigPath, newGitConfigPath); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 yield fs.promises.writeFile(newGitConfigPath, ''); | ||||||
|  |             } | ||||||
|  |             // Configure the token
 | ||||||
|  |             try { | ||||||
|  |                 core.info(`Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes`); | ||||||
|  |                 this.git.setEnvironmentVariable('HOME', this.temporaryHomePath); | ||||||
|  |                 yield this.configureToken(newGitConfigPath, true); | ||||||
|  |             } | ||||||
|  |             catch (err) { | ||||||
|  |                 // Unset in case somehow written to the real global config
 | ||||||
|  |                 core.info('Encountered an error when attempting to configure token. Attempting unconfigure.'); | ||||||
|  |                 yield this.git.tryConfigUnset(this.tokenConfigKey, true); | ||||||
|  |                 throw err; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     configureSubmoduleAuth() { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             if (this.settings.persistCredentials) { | ||||||
|  |                 // 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
 | ||||||
|  |                 const output = yield this.git.submoduleForeach(`git config "${this.tokenConfigKey}" "${this.tokenPlaceholderConfigValue}" && git config --local --show-origin --name-only --get-regexp remote.origin.url`, this.settings.nestedSubmodules); | ||||||
|  |                 // Replace the placeholder
 | ||||||
|  |                 const configPaths = output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || []; | ||||||
|  |                 for (const configPath of configPaths) { | ||||||
|  |                     core.debug(`Replacing token placeholder in '${configPath}'`); | ||||||
|  |                     this.replaceTokenPlaceholder(configPath); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|     removeAuth() { |     removeAuth() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             yield this.removeToken(); |             yield this.removeToken(); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     configureToken() { |     removeGlobalAuth() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             core.info(`Unsetting HOME override`); | ||||||
|  |             this.git.removeEnvironmentVariable('HOME'); | ||||||
|  |             yield io.rmRF(this.temporaryHomePath); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     configureToken(configPath, globalConfig) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             // Validate args
 | ||||||
|  |             assert.ok((configPath && globalConfig) || (!configPath && !globalConfig), 'Unexpected configureToken parameter combinations'); | ||||||
|  |             // Default config path
 | ||||||
|  |             if (!configPath && !globalConfig) { | ||||||
|  |                 configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config'); | ||||||
|  |             } | ||||||
|             // 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 placeholder = `AUTHORIZATION: basic ***`; |             yield this.git.config(this.tokenConfigKey, this.tokenPlaceholderConfigValue, globalConfig); | ||||||
|             yield this.git.config(EXTRA_HEADER_KEY, placeholder); |             // Replace the placeholder
 | ||||||
|             // Determine the basic credential value
 |             yield this.replaceTokenPlaceholder(configPath || ''); | ||||||
|             const basicCredential = Buffer.from(`x-access-token:${this.settings.authToken}`, 'utf8').toString('base64'); |         }); | ||||||
|             core.setSecret(basicCredential); |     } | ||||||
|             // Replace the value in the config file
 |     replaceTokenPlaceholder(configPath) { | ||||||
|             const configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config'); |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             assert.ok(configPath, 'configPath is not defined'); | ||||||
|             let content = (yield fs.promises.readFile(configPath)).toString(); |             let content = (yield fs.promises.readFile(configPath)).toString(); | ||||||
|             const placeholderIndex = content.indexOf(placeholder); |             const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue); | ||||||
|             if (placeholderIndex < 0 || |             if (placeholderIndex < 0 || | ||||||
|                 placeholderIndex != content.lastIndexOf(placeholder)) { |                 placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue)) { | ||||||
|                 throw new Error('Unable to replace auth placeholder in .git/config'); |                 throw new Error(`Unable to replace auth placeholder in ${configPath}`); | ||||||
|             } |             } | ||||||
|             content = content.replace(placeholder, `AUTHORIZATION: basic ${basicCredential}`); |             assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined'); | ||||||
|  |             content = content.replace(this.tokenPlaceholderConfigValue, this.tokenConfigValue); | ||||||
|             yield fs.promises.writeFile(configPath, content); |             yield fs.promises.writeFile(configPath, content); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     removeToken() { |     removeToken() { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             // HTTP extra header
 |             // HTTP extra header
 | ||||||
|             yield this.removeGitConfig(EXTRA_HEADER_KEY); |             yield this.removeGitConfig(this.tokenConfigKey); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     removeGitConfig(configKey) { |     removeGitConfig(configKey) { | ||||||
| @ -5138,6 +5225,8 @@ class GitAuthHelper { | |||||||
|                 // Load the config contents
 |                 // Load the config contents
 | ||||||
|                 core.warning(`Failed to remove '${configKey}' from the git config`); |                 core.warning(`Failed to remove '${configKey}' from the git config`); | ||||||
|             } |             } | ||||||
|  |             const pattern = regexpHelper.escape(configKey); | ||||||
|  |             yield this.git.submoduleForeach(`git config --local --name-only --get-regexp ${pattern} && git config --local --unset-all ${configKey} || :`, true); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -5172,6 +5261,7 @@ const exec = __importStar(__webpack_require__(986)); | |||||||
| const fshelper = __importStar(__webpack_require__(618)); | const fshelper = __importStar(__webpack_require__(618)); | ||||||
| const io = __importStar(__webpack_require__(1)); | const io = __importStar(__webpack_require__(1)); | ||||||
| const path = __importStar(__webpack_require__(622)); | const path = __importStar(__webpack_require__(622)); | ||||||
|  | const regexpHelper = __importStar(__webpack_require__(528)); | ||||||
| const retryHelper = __importStar(__webpack_require__(587)); | const retryHelper = __importStar(__webpack_require__(587)); | ||||||
| const git_version_1 = __webpack_require__(559); | const git_version_1 = __webpack_require__(559); | ||||||
| // Auth header not supported before 2.9
 | // Auth header not supported before 2.9
 | ||||||
| @ -5263,17 +5353,26 @@ class GitCommandManager { | |||||||
|             yield this.execGit(args); |             yield this.execGit(args); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     config(configKey, configValue) { |     config(configKey, configValue, globalConfig) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             yield this.execGit(['config', '--local', configKey, configValue]); |             yield this.execGit([ | ||||||
|  |                 'config', | ||||||
|  |                 globalConfig ? '--global' : '--local', | ||||||
|  |                 configKey, | ||||||
|  |                 configValue | ||||||
|  |             ]); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     configExists(configKey) { |     configExists(configKey, globalConfig) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             const pattern = configKey.replace(/[^a-zA-Z0-9_]/g, x => { |             const pattern = regexpHelper.escape(configKey); | ||||||
|                 return `\\${x}`; |             const output = yield this.execGit([ | ||||||
|             }); |                 'config', | ||||||
|             const output = yield this.execGit(['config', '--local', '--name-only', '--get-regexp', pattern], true); |                 globalConfig ? '--global' : '--local', | ||||||
|  |                 '--name-only', | ||||||
|  |                 '--get-regexp', | ||||||
|  |                 pattern | ||||||
|  |             ], true); | ||||||
|             return output.exitCode === 0; |             return output.exitCode === 0; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @ -5343,9 +5442,45 @@ class GitCommandManager { | |||||||
|             yield this.execGit(['remote', 'add', remoteName, remoteUrl]); |             yield this.execGit(['remote', 'add', remoteName, remoteUrl]); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |     removeEnvironmentVariable(name) { | ||||||
|  |         delete this.gitEnv[name]; | ||||||
|  |     } | ||||||
|     setEnvironmentVariable(name, value) { |     setEnvironmentVariable(name, value) { | ||||||
|         this.gitEnv[name] = value; |         this.gitEnv[name] = value; | ||||||
|     } |     } | ||||||
|  |     submoduleForeach(command, recursive) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             const args = ['submodule', 'foreach']; | ||||||
|  |             if (recursive) { | ||||||
|  |                 args.push('--recursive'); | ||||||
|  |             } | ||||||
|  |             args.push(command); | ||||||
|  |             const output = yield this.execGit(args); | ||||||
|  |             return output.stdout; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     submoduleSync(recursive) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             const args = ['submodule', 'sync']; | ||||||
|  |             if (recursive) { | ||||||
|  |                 args.push('--recursive'); | ||||||
|  |             } | ||||||
|  |             yield this.execGit(args); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     submoduleUpdate(fetchDepth, recursive) { | ||||||
|  |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|  |             const args = ['-c', 'protocol.version=2']; | ||||||
|  |             args.push('submodule', 'update', '--init', '--force'); | ||||||
|  |             if (fetchDepth > 0) { | ||||||
|  |                 args.push(`--depth=${fetchDepth}`); | ||||||
|  |             } | ||||||
|  |             if (recursive) { | ||||||
|  |                 args.push('--recursive'); | ||||||
|  |             } | ||||||
|  |             yield this.execGit(args); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|     tagExists(pattern) { |     tagExists(pattern) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             const output = yield this.execGit(['tag', '--list', pattern]); |             const output = yield this.execGit(['tag', '--list', pattern]); | ||||||
| @ -5358,9 +5493,14 @@ class GitCommandManager { | |||||||
|             return output.exitCode === 0; |             return output.exitCode === 0; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|     tryConfigUnset(configKey) { |     tryConfigUnset(configKey, globalConfig) { | ||||||
|         return __awaiter(this, void 0, void 0, function* () { |         return __awaiter(this, void 0, void 0, function* () { | ||||||
|             const output = yield this.execGit(['config', '--local', '--unset-all', configKey], true); |             const output = yield this.execGit([ | ||||||
|  |                 'config', | ||||||
|  |                 globalConfig ? '--global' : '--local', | ||||||
|  |                 '--unset-all', | ||||||
|  |                 configKey | ||||||
|  |             ], true); | ||||||
|             return output.exitCode === 0; |             return output.exitCode === 0; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @ -5551,48 +5691,66 @@ function getSource(settings) { | |||||||
|             core.info(`The repository will be downloaded using the GitHub REST API`); |             core.info(`The repository will be downloaded using the GitHub REST API`); | ||||||
|             core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`); |             core.info(`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`); | ||||||
|             yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); |             yield githubApiHelper.downloadRepository(settings.authToken, settings.repositoryOwner, settings.repositoryName, settings.ref, settings.commit, settings.repositoryPath); | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|         else { |         // Save state for POST action
 | ||||||
|             // Save state for POST action
 |         stateHelper.setRepositoryPath(settings.repositoryPath); | ||||||
|             stateHelper.setRepositoryPath(settings.repositoryPath); |         // Initialize the repository
 | ||||||
|             // Initialize the repository
 |         if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) { | ||||||
|             if (!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))) { |             yield git.init(); | ||||||
|                 yield git.init(); |             yield git.remoteAdd('origin', repositoryUrl); | ||||||
|                 yield git.remoteAdd('origin', repositoryUrl); |         } | ||||||
|  |         // Disable automatic garbage collection
 | ||||||
|  |         if (!(yield git.tryDisableAutomaticGarbageCollection())) { | ||||||
|  |             core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`); | ||||||
|  |         } | ||||||
|  |         const authHelper = gitAuthHelper.createAuthHelper(git, settings); | ||||||
|  |         try { | ||||||
|  |             // Configure auth
 | ||||||
|  |             yield authHelper.configureAuth(); | ||||||
|  |             // LFS install
 | ||||||
|  |             if (settings.lfs) { | ||||||
|  |                 yield git.lfsInstall(); | ||||||
|             } |             } | ||||||
|             // Disable automatic garbage collection
 |             // Fetch
 | ||||||
|             if (!(yield git.tryDisableAutomaticGarbageCollection())) { |             const refSpec = refHelper.getRefSpec(settings.ref, settings.commit); | ||||||
|                 core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`); |             yield git.fetch(settings.fetchDepth, refSpec); | ||||||
|  |             // Checkout info
 | ||||||
|  |             const checkoutInfo = yield refHelper.getCheckoutInfo(git, settings.ref, settings.commit); | ||||||
|  |             // LFS fetch
 | ||||||
|  |             // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
 | ||||||
|  |             // Explicit lfs fetch will fetch lfs objects in parallel.
 | ||||||
|  |             if (settings.lfs) { | ||||||
|  |                 yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref); | ||||||
|             } |             } | ||||||
|             const authHelper = gitAuthHelper.createAuthHelper(git, settings); |             // Checkout
 | ||||||
|             try { |             yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint); | ||||||
|                 // Configure auth
 |             // Submodules
 | ||||||
|                 yield authHelper.configureAuth(); |             if (settings.submodules) { | ||||||
|                 // LFS install
 |                 try { | ||||||
|                 if (settings.lfs) { |                     // Temporarily override global config
 | ||||||
|                     yield git.lfsInstall(); |                     yield authHelper.configureGlobalAuth(); | ||||||
|  |                     // Checkout submodules
 | ||||||
|  |                     yield git.submoduleSync(settings.nestedSubmodules); | ||||||
|  |                     yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules); | ||||||
|  |                     yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules); | ||||||
|  |                     // Persist credentials
 | ||||||
|  |                     if (settings.persistCredentials) { | ||||||
|  |                         yield authHelper.configureSubmoduleAuth(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 // Fetch
 |                 finally { | ||||||
|                 const refSpec = refHelper.getRefSpec(settings.ref, settings.commit); |                     // Remove temporary global config override
 | ||||||
|                 yield git.fetch(settings.fetchDepth, refSpec); |                     yield authHelper.removeGlobalAuth(); | ||||||
|                 // Checkout info
 |  | ||||||
|                 const checkoutInfo = yield refHelper.getCheckoutInfo(git, settings.ref, settings.commit); |  | ||||||
|                 // LFS fetch
 |  | ||||||
|                 // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
 |  | ||||||
|                 // Explicit lfs fetch will fetch lfs objects in parallel.
 |  | ||||||
|                 if (settings.lfs) { |  | ||||||
|                     yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref); |  | ||||||
|                 } |                 } | ||||||
|                 // Checkout
 |  | ||||||
|                 yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint); |  | ||||||
|                 // Dump some info about the checked out commit
 |  | ||||||
|                 yield git.log1(); |  | ||||||
|             } |             } | ||||||
|             finally { |             // Dump some info about the checked out commit
 | ||||||
|                 // Remove auth
 |             yield git.log1(); | ||||||
|                 if (!settings.persistCredentials) { |         } | ||||||
|                     yield authHelper.removeAuth(); |         finally { | ||||||
|                 } |             // Remove auth
 | ||||||
|  |             if (!settings.persistCredentials) { | ||||||
|  |                 yield authHelper.removeAuth(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| @ -9428,6 +9586,22 @@ module.exports.Singular = Hook.Singular | |||||||
| module.exports.Collection = Hook.Collection | module.exports.Collection = Hook.Collection | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | /***/ }), | ||||||
|  | 
 | ||||||
|  | /***/ 528: | ||||||
|  | /***/ (function(__unusedmodule, exports) { | ||||||
|  | 
 | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||||||
|  | function escape(value) { | ||||||
|  |     return value.replace(/[^a-zA-Z0-9_]/g, x => { | ||||||
|  |         return `\\${x}`; | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | exports.escape = escape; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| /***/ }), | /***/ }), | ||||||
| 
 | 
 | ||||||
| /***/ 529: | /***/ 529: | ||||||
| @ -13731,10 +13905,6 @@ function getInputs() { | |||||||
|     // Clean
 |     // Clean
 | ||||||
|     result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'; |     result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'; | ||||||
|     core.debug(`clean = ${result.clean}`); |     core.debug(`clean = ${result.clean}`); | ||||||
|     // Submodules
 |  | ||||||
|     if (core.getInput('submodules')) { |  | ||||||
|         throw new Error("The input 'submodules' is not supported in actions/checkout@v2"); |  | ||||||
|     } |  | ||||||
|     // Fetch depth
 |     // Fetch depth
 | ||||||
|     result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')); |     result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')); | ||||||
|     if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { |     if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { | ||||||
| @ -13744,6 +13914,19 @@ function getInputs() { | |||||||
|     // LFS
 |     // LFS
 | ||||||
|     result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'; |     result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE'; | ||||||
|     core.debug(`lfs = ${result.lfs}`); |     core.debug(`lfs = ${result.lfs}`); | ||||||
|  |     // Submodules
 | ||||||
|  |     result.submodules = false; | ||||||
|  |     result.nestedSubmodules = false; | ||||||
|  |     const submodulesString = (core.getInput('submodules') || '').toUpperCase(); | ||||||
|  |     if (submodulesString == 'RECURSIVE') { | ||||||
|  |         result.submodules = true; | ||||||
|  |         result.nestedSubmodules = true; | ||||||
|  |     } | ||||||
|  |     else if (submodulesString == 'TRUE') { | ||||||
|  |         result.submodules = true; | ||||||
|  |     } | ||||||
|  |     core.debug(`submodules = ${result.submodules}`); | ||||||
|  |     core.debug(`recursive submodules = ${result.nestedSubmodules}`); | ||||||
|     // Auth token
 |     // Auth token
 | ||||||
|     result.authToken = core.getInput('token'); |     result.authToken = core.getInput('token'); | ||||||
|     // Persist credentials
 |     // Persist credentials
 | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ import * as fs from 'fs' | |||||||
| import * as io from '@actions/io' | import * as io from '@actions/io' | ||||||
| import * as os from 'os' | import * as os from 'os' | ||||||
| import * as path from 'path' | import * as path from 'path' | ||||||
|  | import * as regexpHelper from './regexp-helper' | ||||||
| import * as stateHelper from './state-helper' | import * as stateHelper from './state-helper' | ||||||
| import {default as uuid} from 'uuid/v4' | import {default as uuid} from 'uuid/v4' | ||||||
| import {IGitCommandManager} from './git-command-manager' | import {IGitCommandManager} from './git-command-manager' | ||||||
| @ -12,11 +13,13 @@ import {IGitSourceSettings} from './git-source-settings' | |||||||
| 
 | 
 | ||||||
| const IS_WINDOWS = process.platform === 'win32' | const IS_WINDOWS = process.platform === 'win32' | ||||||
| const HOSTNAME = 'github.com' | const HOSTNAME = 'github.com' | ||||||
| const EXTRA_HEADER_KEY = `http.https://${HOSTNAME}/.extraheader` |  | ||||||
| 
 | 
 | ||||||
| export interface IGitAuthHelper { | export interface IGitAuthHelper { | ||||||
|   configureAuth(): Promise<void> |   configureAuth(): Promise<void> | ||||||
|  |   configureGlobalAuth(): Promise<void> | ||||||
|  |   configureSubmoduleAuth(): Promise<void> | ||||||
|   removeAuth(): Promise<void> |   removeAuth(): Promise<void> | ||||||
|  |   removeGlobalAuth(): Promise<void> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function createAuthHelper( | export function createAuthHelper( | ||||||
| @ -27,8 +30,12 @@ export function createAuthHelper( | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class GitAuthHelper { | class GitAuthHelper { | ||||||
|   private git: IGitCommandManager |   private readonly git: IGitCommandManager | ||||||
|   private settings: IGitSourceSettings |   private readonly settings: IGitSourceSettings | ||||||
|  |   private readonly tokenConfigKey: string = `http.https://${HOSTNAME}/.extraheader` | ||||||
|  |   private readonly tokenPlaceholderConfigValue: string | ||||||
|  |   private temporaryHomePath = '' | ||||||
|  |   private tokenConfigValue: string | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     gitCommandManager: IGitCommandManager, |     gitCommandManager: IGitCommandManager, | ||||||
| @ -36,6 +43,15 @@ class GitAuthHelper { | |||||||
|   ) { |   ) { | ||||||
|     this.git = gitCommandManager |     this.git = gitCommandManager | ||||||
|     this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) |     this.settings = gitSourceSettings || (({} as unknown) as IGitSourceSettings) | ||||||
|  | 
 | ||||||
|  |     // Token auth header
 | ||||||
|  |     const basicCredential = Buffer.from( | ||||||
|  |       `x-access-token:${this.settings.authToken}`, | ||||||
|  |       'utf8' | ||||||
|  |     ).toString('base64') | ||||||
|  |     core.setSecret(basicCredential) | ||||||
|  |     this.tokenPlaceholderConfigValue = `AUTHORIZATION: basic ***` | ||||||
|  |     this.tokenConfigValue = `AUTHORIZATION: basic ${basicCredential}` | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async configureAuth(): Promise<void> { |   async configureAuth(): Promise<void> { | ||||||
| @ -46,48 +62,132 @@ class GitAuthHelper { | |||||||
|     await this.configureToken() |     await this.configureToken() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async configureGlobalAuth(): Promise<void> { | ||||||
|  |     // Create a temp home directory
 | ||||||
|  |     const runnerTemp = process.env['RUNNER_TEMP'] || '' | ||||||
|  |     assert.ok(runnerTemp, 'RUNNER_TEMP is not defined') | ||||||
|  |     const uniqueId = uuid() | ||||||
|  |     this.temporaryHomePath = path.join(runnerTemp, uniqueId) | ||||||
|  |     await fs.promises.mkdir(this.temporaryHomePath, {recursive: true}) | ||||||
|  | 
 | ||||||
|  |     // Copy the global git config
 | ||||||
|  |     const gitConfigPath = path.join( | ||||||
|  |       process.env['HOME'] || os.homedir(), | ||||||
|  |       '.gitconfig' | ||||||
|  |     ) | ||||||
|  |     const newGitConfigPath = path.join(this.temporaryHomePath, '.gitconfig') | ||||||
|  |     let configExists = false | ||||||
|  |     try { | ||||||
|  |       await fs.promises.stat(gitConfigPath) | ||||||
|  |       configExists = true | ||||||
|  |     } catch (err) { | ||||||
|  |       if (err.code !== 'ENOENT') { | ||||||
|  |         throw err | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if (configExists) { | ||||||
|  |       core.info(`Copying '${gitConfigPath}' to '${newGitConfigPath}'`) | ||||||
|  |       await io.cp(gitConfigPath, newGitConfigPath) | ||||||
|  |     } else { | ||||||
|  |       await fs.promises.writeFile(newGitConfigPath, '') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Configure the token
 | ||||||
|  |     try { | ||||||
|  |       core.info( | ||||||
|  |         `Temporarily overriding HOME='${this.temporaryHomePath}' before making global git config changes` | ||||||
|  |       ) | ||||||
|  |       this.git.setEnvironmentVariable('HOME', this.temporaryHomePath) | ||||||
|  |       await this.configureToken(newGitConfigPath, true) | ||||||
|  |     } catch (err) { | ||||||
|  |       // Unset in case somehow written to the real global config
 | ||||||
|  |       core.info( | ||||||
|  |         'Encountered an error when attempting to configure token. Attempting unconfigure.' | ||||||
|  |       ) | ||||||
|  |       await this.git.tryConfigUnset(this.tokenConfigKey, true) | ||||||
|  |       throw err | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async configureSubmoduleAuth(): Promise<void> { | ||||||
|  |     if (this.settings.persistCredentials) { | ||||||
|  |       // 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
 | ||||||
|  |       const output = await this.git.submoduleForeach( | ||||||
|  |         `git config "${this.tokenConfigKey}" "${this.tokenPlaceholderConfigValue}" && git config --local --show-origin --name-only --get-regexp remote.origin.url`, | ||||||
|  |         this.settings.nestedSubmodules | ||||||
|  |       ) | ||||||
|  | 
 | ||||||
|  |       // Replace the placeholder
 | ||||||
|  |       const configPaths: string[] = | ||||||
|  |         output.match(/(?<=(^|\n)file:)[^\t]+(?=\tremote\.origin\.url)/g) || [] | ||||||
|  |       for (const configPath of configPaths) { | ||||||
|  |         core.debug(`Replacing token placeholder in '${configPath}'`) | ||||||
|  |         this.replaceTokenPlaceholder(configPath) | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async removeAuth(): Promise<void> { |   async removeAuth(): Promise<void> { | ||||||
|     await this.removeToken() |     await this.removeToken() | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async configureToken(): Promise<void> { |   async removeGlobalAuth(): Promise<void> { | ||||||
|  |     core.info(`Unsetting HOME override`) | ||||||
|  |     this.git.removeEnvironmentVariable('HOME') | ||||||
|  |     await io.rmRF(this.temporaryHomePath) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async configureToken( | ||||||
|  |     configPath?: string, | ||||||
|  |     globalConfig?: boolean | ||||||
|  |   ): Promise<void> { | ||||||
|  |     // Validate args
 | ||||||
|  |     assert.ok( | ||||||
|  |       (configPath && globalConfig) || (!configPath && !globalConfig), | ||||||
|  |       'Unexpected configureToken parameter combinations' | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // Default config path
 | ||||||
|  |     if (!configPath && !globalConfig) { | ||||||
|  |       configPath = path.join(this.git.getWorkingDirectory(), '.git', 'config') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // 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 placeholder = `AUTHORIZATION: basic ***` |     await this.git.config( | ||||||
|     await this.git.config(EXTRA_HEADER_KEY, placeholder) |       this.tokenConfigKey, | ||||||
| 
 |       this.tokenPlaceholderConfigValue, | ||||||
|     // Determine the basic credential value
 |       globalConfig | ||||||
|     const basicCredential = Buffer.from( |  | ||||||
|       `x-access-token:${this.settings.authToken}`, |  | ||||||
|       'utf8' |  | ||||||
|     ).toString('base64') |  | ||||||
|     core.setSecret(basicCredential) |  | ||||||
| 
 |  | ||||||
|     // Replace the value in the config file
 |  | ||||||
|     const configPath = path.join( |  | ||||||
|       this.git.getWorkingDirectory(), |  | ||||||
|       '.git', |  | ||||||
|       'config' |  | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|  |     // Replace the placeholder
 | ||||||
|  |     await this.replaceTokenPlaceholder(configPath || '') | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private async replaceTokenPlaceholder(configPath: string): Promise<void> { | ||||||
|  |     assert.ok(configPath, 'configPath is not defined') | ||||||
|     let content = (await fs.promises.readFile(configPath)).toString() |     let content = (await fs.promises.readFile(configPath)).toString() | ||||||
|     const placeholderIndex = content.indexOf(placeholder) |     const placeholderIndex = content.indexOf(this.tokenPlaceholderConfigValue) | ||||||
|     if ( |     if ( | ||||||
|       placeholderIndex < 0 || |       placeholderIndex < 0 || | ||||||
|       placeholderIndex != content.lastIndexOf(placeholder) |       placeholderIndex != content.lastIndexOf(this.tokenPlaceholderConfigValue) | ||||||
|     ) { |     ) { | ||||||
|       throw new Error('Unable to replace auth placeholder in .git/config') |       throw new Error(`Unable to replace auth placeholder in ${configPath}`) | ||||||
|     } |     } | ||||||
|  |     assert.ok(this.tokenConfigValue, 'tokenConfigValue is not defined') | ||||||
|     content = content.replace( |     content = content.replace( | ||||||
|       placeholder, |       this.tokenPlaceholderConfigValue, | ||||||
|       `AUTHORIZATION: basic ${basicCredential}` |       this.tokenConfigValue | ||||||
|     ) |     ) | ||||||
|     await fs.promises.writeFile(configPath, content) |     await fs.promises.writeFile(configPath, content) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async removeToken(): Promise<void> { |   private async removeToken(): Promise<void> { | ||||||
|     // HTTP extra header
 |     // HTTP extra header
 | ||||||
|     await this.removeGitConfig(EXTRA_HEADER_KEY) |     await this.removeGitConfig(this.tokenConfigKey) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async removeGitConfig(configKey: string): Promise<void> { |   private async removeGitConfig(configKey: string): Promise<void> { | ||||||
| @ -98,5 +198,11 @@ class GitAuthHelper { | |||||||
|       // Load the config contents
 |       // Load the config contents
 | ||||||
|       core.warning(`Failed to remove '${configKey}' from the git config`) |       core.warning(`Failed to remove '${configKey}' from the git config`) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     const pattern = regexpHelper.escape(configKey) | ||||||
|  |     await this.git.submoduleForeach( | ||||||
|  |       `git config --local --name-only --get-regexp ${pattern} && git config --local --unset-all ${configKey} || :`, | ||||||
|  |       true | ||||||
|  |     ) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import * as exec from '@actions/exec' | |||||||
| import * as fshelper from './fs-helper' | import * as fshelper from './fs-helper' | ||||||
| import * as io from '@actions/io' | import * as io from '@actions/io' | ||||||
| import * as path from 'path' | import * as path from 'path' | ||||||
|  | import * as regexpHelper from './regexp-helper' | ||||||
| import * as retryHelper from './retry-helper' | import * as retryHelper from './retry-helper' | ||||||
| import {GitVersion} from './git-version' | import {GitVersion} from './git-version' | ||||||
| 
 | 
 | ||||||
| @ -16,8 +17,12 @@ export interface IGitCommandManager { | |||||||
|   branchList(remote: boolean): Promise<string[]> |   branchList(remote: boolean): Promise<string[]> | ||||||
|   checkout(ref: string, startPoint: string): Promise<void> |   checkout(ref: string, startPoint: string): Promise<void> | ||||||
|   checkoutDetach(): Promise<void> |   checkoutDetach(): Promise<void> | ||||||
|   config(configKey: string, configValue: string): Promise<void> |   config( | ||||||
|   configExists(configKey: string): Promise<boolean> |     configKey: string, | ||||||
|  |     configValue: string, | ||||||
|  |     globalConfig?: boolean | ||||||
|  |   ): Promise<void> | ||||||
|  |   configExists(configKey: string, globalConfig?: boolean): Promise<boolean> | ||||||
|   fetch(fetchDepth: number, refSpec: string[]): Promise<void> |   fetch(fetchDepth: number, refSpec: string[]): Promise<void> | ||||||
|   getWorkingDirectory(): string |   getWorkingDirectory(): string | ||||||
|   init(): Promise<void> |   init(): Promise<void> | ||||||
| @ -26,10 +31,14 @@ export interface IGitCommandManager { | |||||||
|   lfsInstall(): Promise<void> |   lfsInstall(): Promise<void> | ||||||
|   log1(): Promise<void> |   log1(): Promise<void> | ||||||
|   remoteAdd(remoteName: string, remoteUrl: string): Promise<void> |   remoteAdd(remoteName: string, remoteUrl: string): Promise<void> | ||||||
|  |   removeEnvironmentVariable(name: string): void | ||||||
|   setEnvironmentVariable(name: string, value: string): void |   setEnvironmentVariable(name: string, value: string): void | ||||||
|  |   submoduleForeach(command: string, recursive: boolean): Promise<string> | ||||||
|  |   submoduleSync(recursive: boolean): Promise<void> | ||||||
|  |   submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> | ||||||
|   tagExists(pattern: string): Promise<boolean> |   tagExists(pattern: string): Promise<boolean> | ||||||
|   tryClean(): Promise<boolean> |   tryClean(): Promise<boolean> | ||||||
|   tryConfigUnset(configKey: string): Promise<boolean> |   tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean> | ||||||
|   tryDisableAutomaticGarbageCollection(): Promise<boolean> |   tryDisableAutomaticGarbageCollection(): Promise<boolean> | ||||||
|   tryGetFetchUrl(): Promise<string> |   tryGetFetchUrl(): Promise<string> | ||||||
|   tryReset(): Promise<boolean> |   tryReset(): Promise<boolean> | ||||||
| @ -124,16 +133,32 @@ class GitCommandManager { | |||||||
|     await this.execGit(args) |     await this.execGit(args) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async config(configKey: string, configValue: string): Promise<void> { |   async config( | ||||||
|     await this.execGit(['config', '--local', configKey, configValue]) |     configKey: string, | ||||||
|  |     configValue: string, | ||||||
|  |     globalConfig?: boolean | ||||||
|  |   ): Promise<void> { | ||||||
|  |     await this.execGit([ | ||||||
|  |       'config', | ||||||
|  |       globalConfig ? '--global' : '--local', | ||||||
|  |       configKey, | ||||||
|  |       configValue | ||||||
|  |     ]) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async configExists(configKey: string): Promise<boolean> { |   async configExists( | ||||||
|     const pattern = configKey.replace(/[^a-zA-Z0-9_]/g, x => { |     configKey: string, | ||||||
|       return `\\${x}` |     globalConfig?: boolean | ||||||
|     }) |   ): Promise<boolean> { | ||||||
|  |     const pattern = regexpHelper.escape(configKey) | ||||||
|     const output = await this.execGit( |     const output = await this.execGit( | ||||||
|       ['config', '--local', '--name-only', '--get-regexp', pattern], |       [ | ||||||
|  |         'config', | ||||||
|  |         globalConfig ? '--global' : '--local', | ||||||
|  |         '--name-only', | ||||||
|  |         '--get-regexp', | ||||||
|  |         pattern | ||||||
|  |       ], | ||||||
|       true |       true | ||||||
|     ) |     ) | ||||||
|     return output.exitCode === 0 |     return output.exitCode === 0 | ||||||
| @ -208,10 +233,48 @@ class GitCommandManager { | |||||||
|     await this.execGit(['remote', 'add', remoteName, remoteUrl]) |     await this.execGit(['remote', 'add', remoteName, remoteUrl]) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   removeEnvironmentVariable(name: string): void { | ||||||
|  |     delete this.gitEnv[name] | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   setEnvironmentVariable(name: string, value: string): void { |   setEnvironmentVariable(name: string, value: string): void { | ||||||
|     this.gitEnv[name] = value |     this.gitEnv[name] = value | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   async submoduleForeach(command: string, recursive: boolean): Promise<string> { | ||||||
|  |     const args = ['submodule', 'foreach'] | ||||||
|  |     if (recursive) { | ||||||
|  |       args.push('--recursive') | ||||||
|  |     } | ||||||
|  |     args.push(command) | ||||||
|  | 
 | ||||||
|  |     const output = await this.execGit(args) | ||||||
|  |     return output.stdout | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async submoduleSync(recursive: boolean): Promise<void> { | ||||||
|  |     const args = ['submodule', 'sync'] | ||||||
|  |     if (recursive) { | ||||||
|  |       args.push('--recursive') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await this.execGit(args) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   async submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> { | ||||||
|  |     const args = ['-c', 'protocol.version=2'] | ||||||
|  |     args.push('submodule', 'update', '--init', '--force') | ||||||
|  |     if (fetchDepth > 0) { | ||||||
|  |       args.push(`--depth=${fetchDepth}`) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (recursive) { | ||||||
|  |       args.push('--recursive') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     await this.execGit(args) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   async tagExists(pattern: string): Promise<boolean> { |   async tagExists(pattern: string): Promise<boolean> { | ||||||
|     const output = await this.execGit(['tag', '--list', pattern]) |     const output = await this.execGit(['tag', '--list', pattern]) | ||||||
|     return !!output.stdout.trim() |     return !!output.stdout.trim() | ||||||
| @ -222,9 +285,17 @@ class GitCommandManager { | |||||||
|     return output.exitCode === 0 |     return output.exitCode === 0 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async tryConfigUnset(configKey: string): Promise<boolean> { |   async tryConfigUnset( | ||||||
|  |     configKey: string, | ||||||
|  |     globalConfig?: boolean | ||||||
|  |   ): Promise<boolean> { | ||||||
|     const output = await this.execGit( |     const output = await this.execGit( | ||||||
|       ['config', '--local', '--unset-all', configKey], |       [ | ||||||
|  |         'config', | ||||||
|  |         globalConfig ? '--global' : '--local', | ||||||
|  |         '--unset-all', | ||||||
|  |         configKey | ||||||
|  |       ], | ||||||
|       true |       true | ||||||
|     ) |     ) | ||||||
|     return output.exitCode === 0 |     return output.exitCode === 0 | ||||||
|  | |||||||
| @ -61,63 +61,91 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> { | |||||||
|       settings.commit, |       settings.commit, | ||||||
|       settings.repositoryPath |       settings.repositoryPath | ||||||
|     ) |     ) | ||||||
|   } else { |     return | ||||||
|     // Save state for POST action
 |   } | ||||||
|     stateHelper.setRepositoryPath(settings.repositoryPath) |  | ||||||
| 
 | 
 | ||||||
|     // Initialize the repository
 |   // Save state for POST action
 | ||||||
|     if ( |   stateHelper.setRepositoryPath(settings.repositoryPath) | ||||||
|       !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) | 
 | ||||||
|     ) { |   // Initialize the repository
 | ||||||
|       await git.init() |   if ( | ||||||
|       await git.remoteAdd('origin', repositoryUrl) |     !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) | ||||||
|  |   ) { | ||||||
|  |     await git.init() | ||||||
|  |     await git.remoteAdd('origin', repositoryUrl) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Disable automatic garbage collection
 | ||||||
|  |   if (!(await git.tryDisableAutomaticGarbageCollection())) { | ||||||
|  |     core.warning( | ||||||
|  |       `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const authHelper = gitAuthHelper.createAuthHelper(git, settings) | ||||||
|  |   try { | ||||||
|  |     // Configure auth
 | ||||||
|  |     await authHelper.configureAuth() | ||||||
|  | 
 | ||||||
|  |     // LFS install
 | ||||||
|  |     if (settings.lfs) { | ||||||
|  |       await git.lfsInstall() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Disable automatic garbage collection
 |     // Fetch
 | ||||||
|     if (!(await git.tryDisableAutomaticGarbageCollection())) { |     const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) | ||||||
|       core.warning( |     await git.fetch(settings.fetchDepth, refSpec) | ||||||
|         `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` | 
 | ||||||
|       ) |     // Checkout info
 | ||||||
|  |     const checkoutInfo = await refHelper.getCheckoutInfo( | ||||||
|  |       git, | ||||||
|  |       settings.ref, | ||||||
|  |       settings.commit | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     // LFS fetch
 | ||||||
|  |     // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
 | ||||||
|  |     // Explicit lfs fetch will fetch lfs objects in parallel.
 | ||||||
|  |     if (settings.lfs) { | ||||||
|  |       await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const authHelper = gitAuthHelper.createAuthHelper(git, settings) |     // Checkout
 | ||||||
|     try { |     await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) | ||||||
|       // Configure auth
 |  | ||||||
|       await authHelper.configureAuth() |  | ||||||
| 
 | 
 | ||||||
|       // LFS install
 |     // Submodules
 | ||||||
|       if (settings.lfs) { |     if (settings.submodules) { | ||||||
|         await git.lfsInstall() |       try { | ||||||
|  |         // Temporarily override global config
 | ||||||
|  |         await authHelper.configureGlobalAuth() | ||||||
|  | 
 | ||||||
|  |         // Checkout submodules
 | ||||||
|  |         await git.submoduleSync(settings.nestedSubmodules) | ||||||
|  |         await git.submoduleUpdate( | ||||||
|  |           settings.fetchDepth, | ||||||
|  |           settings.nestedSubmodules | ||||||
|  |         ) | ||||||
|  |         await git.submoduleForeach( | ||||||
|  |           'git config --local gc.auto 0', | ||||||
|  |           settings.nestedSubmodules | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         // Persist credentials
 | ||||||
|  |         if (settings.persistCredentials) { | ||||||
|  |           await authHelper.configureSubmoduleAuth() | ||||||
|  |         } | ||||||
|  |       } finally { | ||||||
|  |         // Remove temporary global config override
 | ||||||
|  |         await authHelper.removeGlobalAuth() | ||||||
|       } |       } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|       // Fetch
 |     // Dump some info about the checked out commit
 | ||||||
|       const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) |     await git.log1() | ||||||
|       await git.fetch(settings.fetchDepth, refSpec) |   } finally { | ||||||
| 
 |     // Remove auth
 | ||||||
|       // Checkout info
 |     if (!settings.persistCredentials) { | ||||||
|       const checkoutInfo = await refHelper.getCheckoutInfo( |       await authHelper.removeAuth() | ||||||
|         git, |  | ||||||
|         settings.ref, |  | ||||||
|         settings.commit |  | ||||||
|       ) |  | ||||||
| 
 |  | ||||||
|       // LFS fetch
 |  | ||||||
|       // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
 |  | ||||||
|       // Explicit lfs fetch will fetch lfs objects in parallel.
 |  | ||||||
|       if (settings.lfs) { |  | ||||||
|         await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       // Checkout
 |  | ||||||
|       await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) |  | ||||||
| 
 |  | ||||||
|       // Dump some info about the checked out commit
 |  | ||||||
|       await git.log1() |  | ||||||
|     } finally { |  | ||||||
|       // Remove auth
 |  | ||||||
|       if (!settings.persistCredentials) { |  | ||||||
|         await authHelper.removeAuth() |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,6 +7,8 @@ export interface IGitSourceSettings { | |||||||
|   clean: boolean |   clean: boolean | ||||||
|   fetchDepth: number |   fetchDepth: number | ||||||
|   lfs: boolean |   lfs: boolean | ||||||
|  |   submodules: boolean | ||||||
|  |   nestedSubmodules: boolean | ||||||
|   authToken: string |   authToken: string | ||||||
|   persistCredentials: boolean |   persistCredentials: boolean | ||||||
| } | } | ||||||
|  | |||||||
| @ -85,13 +85,6 @@ export function getInputs(): IGitSourceSettings { | |||||||
|   result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE' |   result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE' | ||||||
|   core.debug(`clean = ${result.clean}`) |   core.debug(`clean = ${result.clean}`) | ||||||
| 
 | 
 | ||||||
|   // Submodules
 |  | ||||||
|   if (core.getInput('submodules')) { |  | ||||||
|     throw new Error( |  | ||||||
|       "The input 'submodules' is not supported in actions/checkout@v2" |  | ||||||
|     ) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   // Fetch depth
 |   // Fetch depth
 | ||||||
|   result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')) |   result.fetchDepth = Math.floor(Number(core.getInput('fetch-depth') || '1')) | ||||||
|   if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { |   if (isNaN(result.fetchDepth) || result.fetchDepth < 0) { | ||||||
| @ -103,6 +96,19 @@ export function getInputs(): IGitSourceSettings { | |||||||
|   result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE' |   result.lfs = (core.getInput('lfs') || 'false').toUpperCase() === 'TRUE' | ||||||
|   core.debug(`lfs = ${result.lfs}`) |   core.debug(`lfs = ${result.lfs}`) | ||||||
| 
 | 
 | ||||||
|  |   // Submodules
 | ||||||
|  |   result.submodules = false | ||||||
|  |   result.nestedSubmodules = false | ||||||
|  |   const submodulesString = (core.getInput('submodules') || '').toUpperCase() | ||||||
|  |   if (submodulesString == 'RECURSIVE') { | ||||||
|  |     result.submodules = true | ||||||
|  |     result.nestedSubmodules = true | ||||||
|  |   } else if (submodulesString == 'TRUE') { | ||||||
|  |     result.submodules = true | ||||||
|  |   } | ||||||
|  |   core.debug(`submodules = ${result.submodules}`) | ||||||
|  |   core.debug(`recursive submodules = ${result.nestedSubmodules}`) | ||||||
|  | 
 | ||||||
|   // Auth token
 |   // Auth token
 | ||||||
|   result.authToken = core.getInput('token') |   result.authToken = core.getInput('token') | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										5
									
								
								src/regexp-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/regexp-helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | |||||||
|  | export function escape(value: string): string { | ||||||
|  |   return value.replace(/[^a-zA-Z0-9_]/g, x => { | ||||||
|  |     return `\\${x}` | ||||||
|  |   }) | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user