mirror of
https://github.com/actions/checkout.git
synced 2024-11-14 05:26:32 +02:00
add support for submodules (#173)
This commit is contained in:
parent
204620207c
commit
422dc45671
17 changed files with 915 additions and 220 deletions
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…
Reference in a new issue