v6
This commit is contained in:
1 parent
b1d7621cc7
commit
feafc79e51
27 files changed
+1713
-626
No files matched your search
+323
-261
@@ -86,16 +86,29 @@ describe('git-auth-helper tests', () => {
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert config
|
||||
const configContent = (
|
||||
// Assert config - check that .git/config contains includeIf entries
|
||||
const localConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
localConfigContent.indexOf('includeIf.gitdir:')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Assert credentials config file contains the actual credentials
|
||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(1)
|
||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
||||
const credentialsContent = (
|
||||
await fs.promises.readFile(credentialsConfigPath)
|
||||
).toString()
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
credentialsContent.indexOf(
|
||||
`http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
@@ -120,7 +133,7 @@ describe('git-auth-helper tests', () => {
|
||||
'inject https://github.com as github server url'
|
||||
it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
|
||||
await testAuthHeader(
|
||||
configureAuth_AcceptsGitHubServerUrl,
|
||||
configureAuth_AcceptsGitHubServerUrlSetToGHEC,
|
||||
'https://github.com'
|
||||
)
|
||||
})
|
||||
@@ -141,12 +154,17 @@ describe('git-auth-helper tests', () => {
|
||||
// Act
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Assert config
|
||||
const configContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
// Assert config - check credentials config file (not local .git/config)
|
||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(1)
|
||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
||||
const credentialsContent = (
|
||||
await fs.promises.readFile(credentialsConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
credentialsContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
@@ -251,13 +269,16 @@ describe('git-auth-helper tests', () => {
|
||||
expectedSshCommand
|
||||
)
|
||||
|
||||
// Asserty git config
|
||||
// Assert git config
|
||||
const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
expect(gitConfigLines).toHaveLength(1)
|
||||
expect(gitConfigLines[0]).toMatch(/^http\./)
|
||||
// Should have includeIf entries pointing to credentials file
|
||||
expect(gitConfigLines.length).toBeGreaterThan(0)
|
||||
expect(
|
||||
gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0)
|
||||
).toBeTruthy()
|
||||
})
|
||||
|
||||
const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
|
||||
@@ -419,8 +440,20 @@ describe('git-auth-helper tests', () => {
|
||||
expect(
|
||||
configContent.indexOf('value-from-global-config')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
// Global config should have include.path pointing to credentials file
|
||||
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Check credentials in the separate config file
|
||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBeGreaterThan(0)
|
||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
||||
const credentialsContent = (
|
||||
await fs.promises.readFile(credentialsConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
credentialsContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
@@ -463,8 +496,20 @@ describe('git-auth-helper tests', () => {
|
||||
const configContent = (
|
||||
await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
|
||||
).toString()
|
||||
// Global config should have include.path pointing to credentials file
|
||||
expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Check credentials in the separate config file
|
||||
const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBeGreaterThan(0)
|
||||
const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
|
||||
const credentialsContent = (
|
||||
await fs.promises.readFile(credentialsConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
configContent.indexOf(
|
||||
credentialsContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
@@ -550,15 +595,15 @@ describe('git-auth-helper tests', () => {
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(4)
|
||||
// Should configure insteadOf (2 calls for two values)
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(
|
||||
/url.*insteadOf.*[email protected]:/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[3][0]).toMatch(
|
||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
|
||||
/url.*insteadOf.*[email protected]:/
|
||||
)
|
||||
}
|
||||
@@ -589,12 +634,12 @@ describe('git-auth-helper tests', () => {
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Assert
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
|
||||
// Should configure sshCommand (1 call)
|
||||
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
|
||||
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
|
||||
/unset-all.*insteadOf/
|
||||
)
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
|
||||
expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
|
||||
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -660,112 +705,81 @@ describe('git-auth-helper tests', () => {
|
||||
await setup(removeAuth_removesToken)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
let gitConfigContent = (
|
||||
|
||||
// Verify includeIf entries exist in local config
|
||||
let localConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
|
||||
expect(
|
||||
localConfigContent.indexOf('includeIf.gitdir:')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Act
|
||||
await authHelper.removeAuth()
|
||||
// Verify both host and container includeIf entries are present
|
||||
const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/')
|
||||
expect(
|
||||
localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path')
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Assert git config
|
||||
gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
// Verify credentials file exists
|
||||
let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(1)
|
||||
const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0])
|
||||
|
||||
// Verify credentials file contains the auth token
|
||||
let credentialsContent = (
|
||||
await fs.promises.readFile(credentialsFilePath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
|
||||
})
|
||||
|
||||
const removeAuth_removesV6StyleCredentials =
|
||||
'removeAuth removes v6 style credentials'
|
||||
it(removeAuth_removesV6StyleCredentials, async () => {
|
||||
// Arrange
|
||||
await setup(removeAuth_removesV6StyleCredentials)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Manually create v6-style credentials that would be left by v6
|
||||
const credentialsFileName =
|
||||
'git-credentials-12345678-1234-1234-1234-123456789abc.config'
|
||||
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
|
||||
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||
|
||||
// Add includeIf entries to local git config (simulating v6 configuration)
|
||||
const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/')
|
||||
await fs.promises.appendFile(
|
||||
localGitConfigPath,
|
||||
`[includeIf "gitdir:${hostGitDir}/"]\n\tpath = ${credentialsFilePath}\n`
|
||||
)
|
||||
await fs.promises.appendFile(
|
||||
localGitConfigPath,
|
||||
`[includeIf "gitdir:/github/workspace/.git/"]\n\tpath = /github/runner_temp/${credentialsFileName}\n`
|
||||
)
|
||||
|
||||
// Verify v6 style config exists
|
||||
let gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
gitConfigContent.indexOf(credentialsFilePath)
|
||||
credentialsContent.indexOf(
|
||||
`http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
|
||||
)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
await fs.promises.stat(credentialsFilePath) // Verify file exists
|
||||
|
||||
// Mock the git methods to handle v6 cleanup
|
||||
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
|
||||
mockTryGetConfigKeys.mockResolvedValue([
|
||||
`includeIf.gitdir:${hostGitDir}/.path`,
|
||||
'includeIf.gitdir:/github/workspace/.git/.path'
|
||||
])
|
||||
|
||||
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
|
||||
mockTryGetConfigValues.mockImplementation(async (key: string) => {
|
||||
if (key === `includeIf.gitdir:${hostGitDir}/.path`) {
|
||||
return [credentialsFilePath]
|
||||
}
|
||||
if (key === 'includeIf.gitdir:/github/workspace/.git/.path') {
|
||||
return [`/github/runner_temp/${credentialsFileName}`]
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
|
||||
any,
|
||||
any
|
||||
>
|
||||
mockTryConfigUnsetValue.mockImplementation(
|
||||
async (
|
||||
key: string,
|
||||
value: string,
|
||||
globalConfig?: boolean,
|
||||
configPath?: string
|
||||
) => {
|
||||
const targetPath = configPath || localGitConfigPath
|
||||
let content = await fs.promises.readFile(targetPath, 'utf8')
|
||||
// Remove the includeIf section
|
||||
const lines = content
|
||||
.split('\n')
|
||||
.filter(line => !line.includes('includeIf') && !line.includes(value))
|
||||
await fs.promises.writeFile(targetPath, lines.join('\n'))
|
||||
return true
|
||||
}
|
||||
// Verify the includeIf entries point to the credentials file
|
||||
const containerCredentialsPath = path.posix.join(
|
||||
'/github/runner_temp',
|
||||
path.basename(credentialsFilePath)
|
||||
)
|
||||
expect(
|
||||
localConfigContent.indexOf(credentialsFilePath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
localConfigContent.indexOf(containerCredentialsPath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Act
|
||||
await authHelper.removeAuth()
|
||||
|
||||
// Assert includeIf entries removed from local git config
|
||||
gitConfigContent = (
|
||||
// Assert all includeIf entries removed from local git config
|
||||
localConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('includeIf')).toBeLessThan(0)
|
||||
expect(gitConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||
expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0)
|
||||
expect(
|
||||
localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`)
|
||||
).toBeLessThan(0)
|
||||
expect(
|
||||
localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path')
|
||||
).toBeLessThan(0)
|
||||
expect(localConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||
expect(localConfigContent.indexOf(containerCredentialsPath)).toBeLessThan(0)
|
||||
|
||||
// Assert credentials config file deleted
|
||||
credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(0)
|
||||
|
||||
// Verify credentials file no longer exists on disk
|
||||
try {
|
||||
await fs.promises.stat(credentialsFilePath)
|
||||
throw new Error('Credentials file should have been deleted')
|
||||
@@ -776,113 +790,108 @@ describe('git-auth-helper tests', () => {
|
||||
}
|
||||
})
|
||||
|
||||
const removeAuth_removesV6StyleCredentialsFromSubmodules =
|
||||
'removeAuth removes v6 style credentials from submodules'
|
||||
it(removeAuth_removesV6StyleCredentialsFromSubmodules, async () => {
|
||||
const removeAuth_removesTokenFromSubmodules =
|
||||
'removeAuth removes token from submodules'
|
||||
it(removeAuth_removesTokenFromSubmodules, async () => {
|
||||
// Arrange
|
||||
await setup(removeAuth_removesV6StyleCredentialsFromSubmodules)
|
||||
await setup(removeAuth_removesTokenFromSubmodules)
|
||||
|
||||
// Create fake submodule config paths
|
||||
const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1')
|
||||
const submodule2Dir = path.join(workspace, '.git', 'modules', 'submodule-2')
|
||||
const submodule1ConfigPath = path.join(submodule1Dir, 'config')
|
||||
const submodule2ConfigPath = path.join(submodule2Dir, 'config')
|
||||
|
||||
await fs.promises.mkdir(submodule1Dir, {recursive: true})
|
||||
await fs.promises.mkdir(submodule2Dir, {recursive: true})
|
||||
await fs.promises.writeFile(submodule1ConfigPath, '')
|
||||
await fs.promises.writeFile(submodule2ConfigPath, '')
|
||||
|
||||
// Mock getSubmoduleConfigPaths to return our fake submodules (for both configure and remove)
|
||||
const mockGetSubmoduleConfigPaths =
|
||||
git.getSubmoduleConfigPaths as jest.Mock<any, any>
|
||||
mockGetSubmoduleConfigPaths.mockResolvedValue([
|
||||
submodule1ConfigPath,
|
||||
submodule2ConfigPath
|
||||
])
|
||||
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
await authHelper.configureSubmoduleAuth()
|
||||
|
||||
// Create v6-style credentials file
|
||||
const credentialsFileName =
|
||||
'git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
|
||||
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||
const basicCredential = Buffer.from(
|
||||
`x-access-token:${settings.authToken}`,
|
||||
'utf8'
|
||||
).toString('base64')
|
||||
const credentialsContent = `[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic ${basicCredential}\n`
|
||||
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||
|
||||
// Add includeIf entries to submodule config
|
||||
const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
|
||||
await fs.promises.appendFile(
|
||||
submodule1ConfigPath,
|
||||
`[includeIf "gitdir:${submodule1GitDir}/"]\n\tpath = ${credentialsFilePath}\n`
|
||||
// Verify credentials file exists
|
||||
let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(1)
|
||||
const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0])
|
||||
|
||||
// Verify submodule config has includeIf entry
|
||||
let submoduleConfigContent = (
|
||||
// Verify submodule 1 config has includeIf entries
|
||||
let submodule1Content = (
|
||||
await fs.promises.readFile(submodule1ConfigPath)
|
||||
).toString()
|
||||
expect(submoduleConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(
|
||||
0
|
||||
)
|
||||
const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
|
||||
expect(
|
||||
submoduleConfigContent.indexOf(credentialsFilePath)
|
||||
submodule1Content.indexOf(`includeIf.gitdir:${submodule1GitDir}.path`)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
submodule1Content.indexOf(credentialsFilePath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Mock getSubmoduleConfigPaths
|
||||
const mockGetSubmoduleConfigPaths =
|
||||
git.getSubmoduleConfigPaths as jest.Mock<any, any>
|
||||
mockGetSubmoduleConfigPaths.mockResolvedValue([submodule1ConfigPath])
|
||||
// Verify submodule 2 config has includeIf entries
|
||||
let submodule2Content = (
|
||||
await fs.promises.readFile(submodule2ConfigPath)
|
||||
).toString()
|
||||
const submodule2GitDir = submodule2Dir.replace(/\\/g, '/')
|
||||
expect(
|
||||
submodule2Content.indexOf(`includeIf.gitdir:${submodule2GitDir}.path`)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
submodule2Content.indexOf(credentialsFilePath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Mock tryGetConfigKeys for submodule
|
||||
const mockTryGetConfigKeys = git.tryGetConfigKeys as jest.Mock<any, any>
|
||||
mockTryGetConfigKeys.mockImplementation(
|
||||
async (pattern: string, globalConfig?: boolean, configPath?: string) => {
|
||||
if (configPath === submodule1ConfigPath) {
|
||||
return [`includeIf.gitdir:${submodule1GitDir}/.path`]
|
||||
}
|
||||
return []
|
||||
}
|
||||
// Verify both host and container paths are in each submodule config
|
||||
const containerCredentialsPath = path.posix.join(
|
||||
'/github/runner_temp',
|
||||
path.basename(credentialsFilePath)
|
||||
)
|
||||
expect(
|
||||
submodule1Content.indexOf(containerCredentialsPath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
submodule2Content.indexOf(containerCredentialsPath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Mock tryGetConfigValues for submodule
|
||||
const mockTryGetConfigValues = git.tryGetConfigValues as jest.Mock<any, any>
|
||||
mockTryGetConfigValues.mockImplementation(
|
||||
async (key: string, globalConfig?: boolean, configPath?: string) => {
|
||||
if (
|
||||
configPath === submodule1ConfigPath &&
|
||||
key === `includeIf.gitdir:${submodule1GitDir}/.path`
|
||||
) {
|
||||
return [credentialsFilePath]
|
||||
}
|
||||
return []
|
||||
}
|
||||
)
|
||||
|
||||
// Mock tryConfigUnsetValue for submodule
|
||||
const mockTryConfigUnsetValue = git.tryConfigUnsetValue as jest.Mock<
|
||||
any,
|
||||
any
|
||||
>
|
||||
mockTryConfigUnsetValue.mockImplementation(
|
||||
async (
|
||||
key: string,
|
||||
value: string,
|
||||
globalConfig?: boolean,
|
||||
configPath?: string
|
||||
) => {
|
||||
const targetPath = configPath || localGitConfigPath
|
||||
let content = await fs.promises.readFile(targetPath, 'utf8')
|
||||
const lines = content
|
||||
.split('\n')
|
||||
.filter(line => !line.includes('includeIf') && !line.includes(value))
|
||||
await fs.promises.writeFile(targetPath, lines.join('\n'))
|
||||
return true
|
||||
}
|
||||
)
|
||||
|
||||
// Act
|
||||
// Act - ensure mock persists for removeAuth
|
||||
mockGetSubmoduleConfigPaths.mockResolvedValue([
|
||||
submodule1ConfigPath,
|
||||
submodule2ConfigPath
|
||||
])
|
||||
await authHelper.removeAuth()
|
||||
|
||||
// Assert submodule includeIf entries removed
|
||||
submoduleConfigContent = (
|
||||
// Assert submodule 1 includeIf entries removed
|
||||
submodule1Content = (
|
||||
await fs.promises.readFile(submodule1ConfigPath)
|
||||
).toString()
|
||||
expect(submoduleConfigContent.indexOf('includeIf')).toBeLessThan(0)
|
||||
expect(submoduleConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||
expect(submodule1Content.indexOf('includeIf.gitdir:')).toBeLessThan(0)
|
||||
expect(submodule1Content.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||
expect(submodule1Content.indexOf(containerCredentialsPath)).toBeLessThan(0)
|
||||
|
||||
// Assert credentials file deleted
|
||||
// Assert submodule 2 includeIf entries removed
|
||||
submodule2Content = (
|
||||
await fs.promises.readFile(submodule2ConfigPath)
|
||||
).toString()
|
||||
expect(submodule2Content.indexOf('includeIf.gitdir:')).toBeLessThan(0)
|
||||
expect(submodule2Content.indexOf(credentialsFilePath)).toBeLessThan(0)
|
||||
expect(submodule2Content.indexOf(containerCredentialsPath)).toBeLessThan(0)
|
||||
|
||||
// Assert credentials config file deleted
|
||||
credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
|
||||
f => f.startsWith('git-credentials-') && f.endsWith('.config')
|
||||
)
|
||||
expect(credentialsFiles.length).toBe(0)
|
||||
|
||||
// Verify credentials file no longer exists on disk
|
||||
try {
|
||||
await fs.promises.stat(credentialsFilePath)
|
||||
throw new Error('Credentials file should have been deleted')
|
||||
@@ -893,65 +902,6 @@ describe('git-auth-helper tests', () => {
|
||||
}
|
||||
})
|
||||
|
||||
const removeAuth_skipsV6CleanupWhenEnvVarSet =
|
||||
'removeAuth skips v6 cleanup when ACTIONS_CHECKOUT_SKIP_V6_CLEANUP is set'
|
||||
it(removeAuth_skipsV6CleanupWhenEnvVarSet, async () => {
|
||||
// Arrange
|
||||
await setup(removeAuth_skipsV6CleanupWhenEnvVarSet)
|
||||
|
||||
// Set the skip environment variable
|
||||
process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'] = '1'
|
||||
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
await authHelper.configureAuth()
|
||||
|
||||
// Create v6-style credentials file in RUNNER_TEMP
|
||||
const credentialsFileName = 'git-credentials-test-uuid-1234-5678.config'
|
||||
const credentialsFilePath = path.join(runnerTemp, credentialsFileName)
|
||||
const credentialsContent =
|
||||
'[http "https://github.com/"]\n\textraheader = AUTHORIZATION: basic token\n'
|
||||
await fs.promises.writeFile(credentialsFilePath, credentialsContent)
|
||||
|
||||
// Add includeIf section to local git config (separate from http.* config)
|
||||
const includeIfSection = `\n[includeIf "gitdir:/some/path/.git/"]\n\tpath = ${credentialsFilePath}\n`
|
||||
await fs.promises.appendFile(localGitConfigPath, includeIfSection)
|
||||
|
||||
// Verify v6 style config exists
|
||||
let gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||
await fs.promises.stat(credentialsFilePath) // Verify file exists
|
||||
|
||||
// Act
|
||||
await authHelper.removeAuth()
|
||||
|
||||
// Assert v5 cleanup still happened (http.* removed)
|
||||
gitConfigContent = (
|
||||
await fs.promises.readFile(localGitConfigPath)
|
||||
).toString()
|
||||
expect(
|
||||
gitConfigContent.indexOf('http.https://github.com/.extraheader')
|
||||
).toBeLessThan(0)
|
||||
|
||||
// Assert v6 cleanup was skipped - includeIf should still be present
|
||||
expect(gitConfigContent.indexOf('includeIf')).toBeGreaterThanOrEqual(0)
|
||||
expect(
|
||||
gitConfigContent.indexOf(credentialsFilePath)
|
||||
).toBeGreaterThanOrEqual(0)
|
||||
|
||||
// Assert credentials file still exists (wasn't deleted)
|
||||
await fs.promises.stat(credentialsFilePath) // File should still exist
|
||||
|
||||
// Assert debug message was logged
|
||||
expect(core.debug).toHaveBeenCalledWith(
|
||||
'Skipping v6 style cleanup due to ACTIONS_CHECKOUT_SKIP_V6_CLEANUP'
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
delete process.env['ACTIONS_CHECKOUT_SKIP_V6_CLEANUP']
|
||||
})
|
||||
|
||||
const removeGlobalConfig_removesOverride =
|
||||
'removeGlobalConfig removes override'
|
||||
it(removeGlobalConfig_removesOverride, async () => {
|
||||
@@ -978,6 +928,52 @@ describe('git-auth-helper tests', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const testCredentialsConfigPath_matchesCredentialsConfigPaths =
|
||||
'testCredentialsConfigPath matches credentials config paths'
|
||||
it(testCredentialsConfigPath_matchesCredentialsConfigPaths, async () => {
|
||||
// Arrange
|
||||
await setup(testCredentialsConfigPath_matchesCredentialsConfigPaths)
|
||||
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||
|
||||
// Get a real credentials config path
|
||||
const credentialsConfigPath = await (
|
||||
authHelper as any
|
||||
).getCredentialsConfigPath()
|
||||
|
||||
// Act & Assert
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(credentialsConfigPath)
|
||||
).toBe(true)
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(
|
||||
'/some/path/git-credentials-12345678-abcd-1234-5678-123456789012.config'
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(
|
||||
'/some/path/git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
|
||||
)
|
||||
).toBe(true)
|
||||
|
||||
// Test invalid paths
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(
|
||||
'/some/path/other-config.config'
|
||||
)
|
||||
).toBe(false)
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(
|
||||
'/some/path/git-credentials-invalid.config'
|
||||
)
|
||||
).toBe(false)
|
||||
expect(
|
||||
(authHelper as any).testCredentialsConfigPath(
|
||||
'/some/path/git-credentials-.config'
|
||||
)
|
||||
).toBe(false)
|
||||
expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
async function setup(testName: string): Promise<void> {
|
||||
@@ -992,6 +988,7 @@ async function setup(testName: string): Promise<void> {
|
||||
await fs.promises.mkdir(tempHomedir, {recursive: true})
|
||||
process.env['RUNNER_TEMP'] = runnerTemp
|
||||
process.env['HOME'] = tempHomedir
|
||||
process.env['GITHUB_WORKSPACE'] = workspace
|
||||
|
||||
// Create git config
|
||||
globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
|
||||
@@ -1010,10 +1007,20 @@ async function setup(testName: string): Promise<void> {
|
||||
checkout: jest.fn(),
|
||||
checkoutDetach: jest.fn(),
|
||||
config: jest.fn(
|
||||
async (key: string, value: string, globalConfig?: boolean) => {
|
||||
const configPath = globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath
|
||||
async (
|
||||
key: string,
|
||||
value: string,
|
||||
globalConfig?: boolean,
|
||||
add?: boolean,
|
||||
configFile?: string
|
||||
) => {
|
||||
const configPath =
|
||||
configFile ||
|
||||
(globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath)
|
||||
// Ensure directory exists
|
||||
await fs.promises.mkdir(path.dirname(configPath), {recursive: true})
|
||||
await fs.promises.appendFile(configPath, `\n${key} ${value}`)
|
||||
}
|
||||
),
|
||||
@@ -1033,6 +1040,7 @@ async function setup(testName: string): Promise<void> {
|
||||
env: {},
|
||||
fetch: jest.fn(),
|
||||
getDefaultBranch: jest.fn(),
|
||||
getSubmoduleConfigPaths: jest.fn(async () => []),
|
||||
getWorkingDirectory: jest.fn(() => workspace),
|
||||
init: jest.fn(),
|
||||
isDetached: jest.fn(),
|
||||
@@ -1071,20 +1079,72 @@ async function setup(testName: string): Promise<void> {
|
||||
return true
|
||||
}
|
||||
),
|
||||
tryConfigUnsetValue: jest.fn(
|
||||
async (
|
||||
key: string,
|
||||
value: string,
|
||||
globalConfig?: boolean,
|
||||
configPath?: string
|
||||
): Promise<boolean> => {
|
||||
const targetConfigPath =
|
||||
configPath ||
|
||||
(globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath)
|
||||
let content = await fs.promises.readFile(targetConfigPath)
|
||||
let lines = content
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
.filter(x => !(x.startsWith(key) && x.includes(value)))
|
||||
await fs.promises.writeFile(targetConfigPath, lines.join('\n'))
|
||||
return true
|
||||
}
|
||||
),
|
||||
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||
tryGetFetchUrl: jest.fn(),
|
||||
getSubmoduleConfigPaths: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryConfigUnsetValue: jest.fn(async () => {
|
||||
return true
|
||||
}),
|
||||
tryGetConfigValues: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryGetConfigKeys: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryGetConfigValues: jest.fn(
|
||||
async (
|
||||
key: string,
|
||||
globalConfig?: boolean,
|
||||
configPath?: string
|
||||
): Promise<string[]> => {
|
||||
const targetConfigPath =
|
||||
configPath ||
|
||||
(globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath)
|
||||
const content = await fs.promises.readFile(targetConfigPath)
|
||||
const lines = content
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x && x.startsWith(key))
|
||||
.map(x => x.substring(key.length).trim())
|
||||
return lines
|
||||
}
|
||||
),
|
||||
tryGetConfigKeys: jest.fn(
|
||||
async (
|
||||
pattern: string,
|
||||
globalConfig?: boolean,
|
||||
configPath?: string
|
||||
): Promise<string[]> => {
|
||||
const targetConfigPath =
|
||||
configPath ||
|
||||
(globalConfig
|
||||
? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
|
||||
: localGitConfigPath)
|
||||
const content = await fs.promises.readFile(targetConfigPath)
|
||||
const lines = content
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter(x => x)
|
||||
const keys = lines
|
||||
.filter(x => new RegExp(pattern).test(x.split(' ')[0]))
|
||||
.map(x => x.split(' ')[0])
|
||||
return [...new Set(keys)] // Remove duplicates
|
||||
}
|
||||
),
|
||||
tryReset: jest.fn(),
|
||||
version: jest.fn()
|
||||
}
|
||||
@@ -1120,6 +1180,7 @@ async function setup(testName: string): Promise<void> {
|
||||
|
||||
async function getActualSshKeyPath(): Promise<string> {
|
||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
|
||||
.sort()
|
||||
.map(x => path.join(runnerTemp, x))
|
||||
if (actualTempFiles.length === 0) {
|
||||
@@ -1133,6 +1194,7 @@ async function getActualSshKeyPath(): Promise<string> {
|
||||
|
||||
async function getActualSshKnownHostsPath(): Promise<string> {
|
||||
let actualTempFiles = (await fs.promises.readdir(runnerTemp))
|
||||
.filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
|
||||
.sort()
|
||||
.map(x => path.join(runnerTemp, x))
|
||||
if (actualTempFiles.length === 0) {
|
||||
|
||||
@@ -108,7 +108,7 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is true', async () => {
|
||||
it('should call execGit with the correct arguments when fetchDepth is 0', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
@@ -122,45 +122,7 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
const refSpec = ['refspec1', 'refspec2']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchDepth: 0,
|
||||
fetchTags: true
|
||||
}
|
||||
|
||||
await git.fetch(refSpec, options)
|
||||
|
||||
expect(mockExec).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
[
|
||||
'-c',
|
||||
'protocol.version=2',
|
||||
'fetch',
|
||||
'--prune',
|
||||
'--no-recurse-submodules',
|
||||
'--filter=filterValue',
|
||||
'origin',
|
||||
'refspec1',
|
||||
'refspec2'
|
||||
],
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchDepth is 0 and fetchTags is false', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
const doSparseCheckout = false
|
||||
git = await commandManager.createCommandManager(
|
||||
workingDirectory,
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
const refSpec = ['refspec1', 'refspec2']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchDepth: 0,
|
||||
fetchTags: false
|
||||
fetchDepth: 0
|
||||
}
|
||||
|
||||
await git.fetch(refSpec, options)
|
||||
@@ -183,7 +145,45 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is false', async () => {
|
||||
it('should call execGit with the correct arguments when fetchDepth is 0 and refSpec includes tags', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
const doSparseCheckout = false
|
||||
git = await commandManager.createCommandManager(
|
||||
workingDirectory,
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchDepth: 0
|
||||
}
|
||||
|
||||
await git.fetch(refSpec, options)
|
||||
|
||||
expect(mockExec).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
[
|
||||
'-c',
|
||||
'protocol.version=2',
|
||||
'fetch',
|
||||
'--no-tags',
|
||||
'--prune',
|
||||
'--no-recurse-submodules',
|
||||
'--filter=filterValue',
|
||||
'origin',
|
||||
'refspec1',
|
||||
'refspec2',
|
||||
'+refs/tags/*:refs/tags/*'
|
||||
],
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchDepth is 1', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
@@ -197,8 +197,7 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
const refSpec = ['refspec1', 'refspec2']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchDepth: 1,
|
||||
fetchTags: false
|
||||
fetchDepth: 1
|
||||
}
|
||||
|
||||
await git.fetch(refSpec, options)
|
||||
@@ -222,7 +221,7 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchDepth is 1 and fetchTags is true', async () => {
|
||||
it('should call execGit with the correct arguments when fetchDepth is 1 and refSpec includes tags', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
@@ -233,11 +232,10 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
const refSpec = ['refspec1', 'refspec2']
|
||||
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchDepth: 1,
|
||||
fetchTags: true
|
||||
fetchDepth: 1
|
||||
}
|
||||
|
||||
await git.fetch(refSpec, options)
|
||||
@@ -248,13 +246,15 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
'-c',
|
||||
'protocol.version=2',
|
||||
'fetch',
|
||||
'--no-tags',
|
||||
'--prune',
|
||||
'--no-recurse-submodules',
|
||||
'--filter=filterValue',
|
||||
'--depth=1',
|
||||
'origin',
|
||||
'refspec1',
|
||||
'refspec2'
|
||||
'refspec2',
|
||||
'+refs/tags/*:refs/tags/*'
|
||||
],
|
||||
expect.any(Object)
|
||||
)
|
||||
@@ -338,7 +338,7 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should call execGit with the correct arguments when fetchTags is true and showProgress is true', async () => {
|
||||
it('should call execGit with the correct arguments when showProgress is true and refSpec includes tags', async () => {
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
@@ -349,10 +349,9 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
const refSpec = ['refspec1', 'refspec2']
|
||||
const refSpec = ['refspec1', 'refspec2', '+refs/tags/*:refs/tags/*']
|
||||
const options = {
|
||||
filter: 'filterValue',
|
||||
fetchTags: true,
|
||||
showProgress: true
|
||||
}
|
||||
|
||||
@@ -364,15 +363,187 @@ describe('Test fetchDepth and fetchTags options', () => {
|
||||
'-c',
|
||||
'protocol.version=2',
|
||||
'fetch',
|
||||
'--no-tags',
|
||||
'--prune',
|
||||
'--no-recurse-submodules',
|
||||
'--progress',
|
||||
'--filter=filterValue',
|
||||
'origin',
|
||||
'refspec1',
|
||||
'refspec2'
|
||||
'refspec2',
|
||||
'+refs/tags/*:refs/tags/*'
|
||||
],
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('repository initialization object format', () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
|
||||
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('initializes SHA-256 repositories with the matching object format', async () => {
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('git version 2.50.1'))
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
git = await commandManager.createCommandManager('test', false, false)
|
||||
|
||||
await git.init('sha256')
|
||||
|
||||
expect(mockExec).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
['init', '--object-format=sha256', 'test'],
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
|
||||
it('initializes SHA-1 repositories with existing default arguments', async () => {
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('git version 2.50.1'))
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
git = await commandManager.createCommandManager('test', false, false)
|
||||
|
||||
await git.init('sha1')
|
||||
|
||||
expect(mockExec).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
['init', 'test'],
|
||||
expect.any(Object)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('git user-agent with orchestration ID', () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn())
|
||||
jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
// Clean up environment variable to prevent test pollution
|
||||
delete process.env['ACTIONS_ORCHESTRATION_ID']
|
||||
})
|
||||
|
||||
it('should include orchestration ID in user-agent when ACTIONS_ORCHESTRATION_ID is set', async () => {
|
||||
const orchId = 'test-orch-id-12345'
|
||||
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
|
||||
|
||||
let capturedEnv: any = null
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('2.18'))
|
||||
}
|
||||
// Capture env on any command
|
||||
capturedEnv = options.env
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
const doSparseCheckout = false
|
||||
git = await commandManager.createCommandManager(
|
||||
workingDirectory,
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
|
||||
// Call a git command to trigger env capture after user-agent is set
|
||||
await git.init()
|
||||
|
||||
// Verify the user agent includes the orchestration ID
|
||||
expect(git).toBeDefined()
|
||||
expect(capturedEnv).toBeDefined()
|
||||
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
|
||||
`git/2.18 (github-actions-checkout) actions_orchestration_id/${orchId}`
|
||||
)
|
||||
})
|
||||
|
||||
it('should sanitize invalid characters in orchestration ID', async () => {
|
||||
const orchId = 'test (with) special/chars'
|
||||
process.env['ACTIONS_ORCHESTRATION_ID'] = orchId
|
||||
|
||||
let capturedEnv: any = null
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('2.18'))
|
||||
}
|
||||
// Capture env on any command
|
||||
capturedEnv = options.env
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
const doSparseCheckout = false
|
||||
git = await commandManager.createCommandManager(
|
||||
workingDirectory,
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
|
||||
// Call a git command to trigger env capture after user-agent is set
|
||||
await git.init()
|
||||
|
||||
// Verify the user agent has sanitized orchestration ID (spaces, parentheses, slash replaced)
|
||||
expect(git).toBeDefined()
|
||||
expect(capturedEnv).toBeDefined()
|
||||
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
|
||||
'git/2.18 (github-actions-checkout) actions_orchestration_id/test__with__special_chars'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => {
|
||||
delete process.env['ACTIONS_ORCHESTRATION_ID']
|
||||
|
||||
let capturedEnv: any = null
|
||||
mockExec.mockImplementation((path, args, options) => {
|
||||
if (args.includes('version')) {
|
||||
options.listeners.stdout(Buffer.from('2.18'))
|
||||
}
|
||||
// Capture env on any command
|
||||
capturedEnv = options.env
|
||||
return 0
|
||||
})
|
||||
jest.spyOn(exec, 'exec').mockImplementation(mockExec)
|
||||
|
||||
const workingDirectory = 'test'
|
||||
const lfs = false
|
||||
const doSparseCheckout = false
|
||||
git = await commandManager.createCommandManager(
|
||||
workingDirectory,
|
||||
lfs,
|
||||
doSparseCheckout
|
||||
)
|
||||
|
||||
// Call a git command to trigger env capture after user-agent is set
|
||||
await git.init()
|
||||
|
||||
// Verify the user agent does NOT contain orchestration ID
|
||||
expect(git).toBeDefined()
|
||||
expect(capturedEnv).toBeDefined()
|
||||
expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe(
|
||||
'git/2.18 (github-actions-checkout)'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -471,6 +471,7 @@ async function setup(testName: string): Promise<void> {
|
||||
configExists: jest.fn(),
|
||||
fetch: jest.fn(),
|
||||
getDefaultBranch: jest.fn(),
|
||||
getSubmoduleConfigPaths: jest.fn(async () => []),
|
||||
getWorkingDirectory: jest.fn(() => repositoryPath),
|
||||
init: jest.fn(),
|
||||
isDetached: jest.fn(),
|
||||
@@ -493,24 +494,15 @@ async function setup(testName: string): Promise<void> {
|
||||
return true
|
||||
}),
|
||||
tryConfigUnset: jest.fn(),
|
||||
tryConfigUnsetValue: jest.fn(),
|
||||
tryDisableAutomaticGarbageCollection: jest.fn(),
|
||||
tryGetFetchUrl: jest.fn(async () => {
|
||||
// Sanity check - this function shouldn't be called when the .git directory doesn't exist
|
||||
await fs.promises.stat(path.join(repositoryPath, '.git'))
|
||||
return repositoryUrl
|
||||
}),
|
||||
getSubmoduleConfigPaths: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryConfigUnsetValue: jest.fn(async () => {
|
||||
return true
|
||||
}),
|
||||
tryGetConfigValues: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryGetConfigKeys: jest.fn(async () => {
|
||||
return []
|
||||
}),
|
||||
tryGetConfigValues: jest.fn(),
|
||||
tryGetConfigKeys: jest.fn(),
|
||||
tryReset: jest.fn(async () => {
|
||||
return true
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as githubApiHelper from '../lib/github-api-helper'
|
||||
|
||||
describe('github-api-helper object format', () => {
|
||||
let getOctokitSpy: jest.SpyInstance
|
||||
let debugSpy: jest.SpyInstance
|
||||
let request: jest.Mock
|
||||
|
||||
function mockHashAlgorithmApi(hashAlgorithm: string): void {
|
||||
request = jest.fn(async () => ({
|
||||
data: {
|
||||
hash_algorithm: hashAlgorithm
|
||||
}
|
||||
}))
|
||||
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
|
||||
request
|
||||
} as any)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('detects SHA-256 from the repository hash algorithm endpoint', async () => {
|
||||
mockHashAlgorithmApi('sha256')
|
||||
|
||||
await expect(
|
||||
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
|
||||
).resolves.toEqual({format: 'sha256', succeeded: true})
|
||||
|
||||
expect(getOctokitSpy).toHaveBeenCalledWith(
|
||||
'token',
|
||||
expect.objectContaining({baseUrl: 'https://api.github.com'})
|
||||
)
|
||||
expect(request).toHaveBeenCalledWith(
|
||||
'GET /repos/{owner}/{repo}/hash-algorithm',
|
||||
{owner: 'owner', repo: 'repo'}
|
||||
)
|
||||
})
|
||||
|
||||
it('detects SHA-1 from the repository hash algorithm endpoint', async () => {
|
||||
mockHashAlgorithmApi('sha1')
|
||||
|
||||
await expect(
|
||||
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
|
||||
).resolves.toEqual({format: 'sha1', succeeded: true})
|
||||
})
|
||||
|
||||
it('detects object format from an existing commit without API calls', async () => {
|
||||
const commitSha =
|
||||
'9422233ca7ee1b17f1e905d0e141faf0c401556c41cdc6acd71c6bd685da2e92'
|
||||
getOctokitSpy = jest.spyOn(github, 'getOctokit')
|
||||
|
||||
await expect(
|
||||
githubApiHelper.tryGetRepositoryObjectFormat(
|
||||
'token',
|
||||
'owner',
|
||||
'repo',
|
||||
undefined,
|
||||
commitSha
|
||||
)
|
||||
).resolves.toEqual({format: 'sha256', succeeded: true})
|
||||
|
||||
expect(getOctokitSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns unsuccessful when the hash algorithm endpoint value is not recognized', async () => {
|
||||
mockHashAlgorithmApi('unknown')
|
||||
|
||||
await expect(
|
||||
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
|
||||
).resolves.toEqual({format: '', succeeded: false})
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
'Unable to determine repository object format from hash-algorithm endpoint'
|
||||
)
|
||||
})
|
||||
|
||||
it('returns unsuccessful when the hash algorithm API lookup fails', async () => {
|
||||
request = jest.fn(async () => {
|
||||
throw new Error('not found')
|
||||
})
|
||||
jest.spyOn(github, 'getOctokit').mockReturnValue({
|
||||
request
|
||||
} as any)
|
||||
|
||||
await expect(
|
||||
githubApiHelper.tryGetRepositoryObjectFormat('token', 'owner', 'repo')
|
||||
).resolves.toEqual({format: '', succeeded: false})
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
'Unable to determine repository object format from hash-algorithm endpoint: not found'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -143,6 +143,16 @@ describe('input-helper tests', () => {
|
||||
expect(settings.commit).toBe('1111111111222222222233333333334444444444')
|
||||
})
|
||||
|
||||
it('sets ref to empty when explicit sha-256', async () => {
|
||||
inputs.ref =
|
||||
'1111111111222222222233333333334444444444555555555566666666667777'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.ref).toBeFalsy()
|
||||
expect(settings.commit).toBe(
|
||||
'1111111111222222222233333333334444444444555555555566666666667777'
|
||||
)
|
||||
})
|
||||
|
||||
it('sets sha to empty when explicit ref', async () => {
|
||||
inputs.ref = 'refs/heads/some-other-ref'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
@@ -160,6 +170,16 @@ describe('input-helper tests', () => {
|
||||
expect(settings.ref).toBe('522d932fae5296da51fdf431934425ecf891c6a2')
|
||||
})
|
||||
|
||||
it('does not reclassify a sha-256 ref as sha when a BOM is prefixed', async () => {
|
||||
inputs.ref =
|
||||
'\uFEFF1111111111222222222233333333334444444444555555555566666666667777'
|
||||
const settings: IGitSourceSettings = await inputHelper.getInputs()
|
||||
expect(settings.commit).toBeFalsy()
|
||||
expect(settings.ref).toBe(
|
||||
'1111111111222222222233333333334444444444555555555566666666667777'
|
||||
)
|
||||
})
|
||||
|
||||
it('treats a sha surrounded by ascii whitespace as a commit', async () => {
|
||||
// ASCII whitespace can only come from the workflow author's YAML (git ref
|
||||
// names cannot contain it), so trimming it and treating the value as a
|
||||
|
||||
+189
-1
@@ -1,8 +1,12 @@
|
||||
import * as assert from 'assert'
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import * as refHelper from '../lib/ref-helper'
|
||||
import {IGitCommandManager} from '../lib/git-command-manager'
|
||||
|
||||
const commit = '1234567890123456789012345678901234567890'
|
||||
const sha256Commit =
|
||||
'1234567890123456789012345678901234567890123456789012345678901234'
|
||||
let git: IGitCommandManager
|
||||
|
||||
describe('ref-helper tests', () => {
|
||||
@@ -37,6 +41,12 @@ describe('ref-helper tests', () => {
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo sha-256 only', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(git, '', sha256Commit)
|
||||
expect(checkoutInfo.ref).toBe(sha256Commit)
|
||||
expect(checkoutInfo.startPoint).toBeFalsy()
|
||||
})
|
||||
|
||||
it('getCheckoutInfo refs/heads/', async () => {
|
||||
const checkoutInfo = await refHelper.getCheckoutInfo(
|
||||
git,
|
||||
@@ -152,7 +162,22 @@ describe('ref-helper tests', () => {
|
||||
it('getRefSpec sha + refs/tags/', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe(`+${commit}:refs/tags/my-tag`)
|
||||
expect(refSpec[0]).toBe(`+refs/tags/my-tag:refs/tags/my-tag`)
|
||||
})
|
||||
|
||||
it('getRefSpec sha + refs/tags/ with fetchTags', async () => {
|
||||
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
|
||||
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', commit, true)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
|
||||
})
|
||||
|
||||
it('getRefSpec sha + refs/heads/ with fetchTags', async () => {
|
||||
// When fetchTags is true, include both the branch refspec and tags wildcard
|
||||
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', commit, true)
|
||||
expect(refSpec.length).toBe(2)
|
||||
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
|
||||
expect(refSpec[1]).toBe(`+${commit}:refs/remotes/origin/my/branch`)
|
||||
})
|
||||
|
||||
it('getRefSpec sha only', async () => {
|
||||
@@ -168,6 +193,14 @@ describe('ref-helper tests', () => {
|
||||
expect(refSpec[1]).toBe('+refs/tags/my-ref*:refs/tags/my-ref*')
|
||||
})
|
||||
|
||||
it('getRefSpec unqualified ref only with fetchTags', async () => {
|
||||
// When fetchTags is true, skip specific tag pattern since wildcard covers all
|
||||
const refSpec = refHelper.getRefSpec('my-ref', '', true)
|
||||
expect(refSpec.length).toBe(2)
|
||||
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
|
||||
expect(refSpec[1]).toBe('+refs/heads/my-ref*:refs/remotes/origin/my-ref*')
|
||||
})
|
||||
|
||||
it('getRefSpec refs/heads/ only', async () => {
|
||||
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '')
|
||||
expect(refSpec.length).toBe(1)
|
||||
@@ -187,4 +220,159 @@ describe('ref-helper tests', () => {
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe('+refs/tags/my-tag:refs/tags/my-tag')
|
||||
})
|
||||
|
||||
it('getRefSpec refs/tags/ only with fetchTags', async () => {
|
||||
// When fetchTags is true, only include tags wildcard (specific tag is redundant)
|
||||
const refSpec = refHelper.getRefSpec('refs/tags/my-tag', '', true)
|
||||
expect(refSpec.length).toBe(1)
|
||||
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
|
||||
})
|
||||
|
||||
it('getRefSpec refs/heads/ only with fetchTags', async () => {
|
||||
// When fetchTags is true, include both the branch refspec and tags wildcard
|
||||
const refSpec = refHelper.getRefSpec('refs/heads/my/branch', '', true)
|
||||
expect(refSpec.length).toBe(2)
|
||||
expect(refSpec[0]).toBe('+refs/tags/*:refs/tags/*')
|
||||
expect(refSpec[1]).toBe(
|
||||
'+refs/heads/my/branch:refs/remotes/origin/my/branch'
|
||||
)
|
||||
})
|
||||
|
||||
describe('checkCommitInfo', () => {
|
||||
const repositoryOwner = 'some-owner'
|
||||
const repositoryName = 'some-repo'
|
||||
const ref = 'refs/pull/123/merge'
|
||||
const sha1Head = '1111111111222222222233333333334444444444'
|
||||
const sha1Base = 'aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd'
|
||||
const sha256Head =
|
||||
'1111111111222222222233333333334444444444555555555566666666667777'
|
||||
const sha256Base =
|
||||
'aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffff0000'
|
||||
let debugSpy: jest.SpyInstance
|
||||
let getOctokitSpy: jest.SpyInstance
|
||||
let repoGetSpy: jest.Mock
|
||||
let originalEventName: string
|
||||
let originalPayload: unknown
|
||||
let originalRef: string
|
||||
let originalSha: string
|
||||
|
||||
function setPullRequestContext(
|
||||
expectedHeadSha: string,
|
||||
expectedBaseSha: string,
|
||||
mergeCommit: string
|
||||
): void {
|
||||
;(github.context as any).eventName = 'pull_request'
|
||||
github.context.ref = ref
|
||||
github.context.sha = mergeCommit
|
||||
;(github.context as any).payload = {
|
||||
action: 'synchronize',
|
||||
after: expectedHeadSha,
|
||||
number: 123,
|
||||
pull_request: {
|
||||
base: {
|
||||
sha: expectedBaseSha
|
||||
}
|
||||
},
|
||||
repository: {
|
||||
private: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
originalEventName = github.context.eventName
|
||||
originalPayload = github.context.payload
|
||||
originalRef = github.context.ref
|
||||
originalSha = github.context.sha
|
||||
|
||||
jest.spyOn(github.context, 'repo', 'get').mockReturnValue({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName
|
||||
})
|
||||
debugSpy = jest.spyOn(core, 'debug').mockImplementation(jest.fn())
|
||||
repoGetSpy = jest.fn(async () => ({}))
|
||||
getOctokitSpy = jest.spyOn(github, 'getOctokit').mockReturnValue({
|
||||
rest: {
|
||||
repos: {
|
||||
get: repoGetSpy
|
||||
}
|
||||
}
|
||||
} as any)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
;(github.context as any).eventName = originalEventName
|
||||
;(github.context as any).payload = originalPayload
|
||||
github.context.ref = originalRef
|
||||
github.context.sha = originalSha
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('returns early for SHA-1 merge commit', async () => {
|
||||
setPullRequestContext(sha1Head, sha1Base, commit)
|
||||
|
||||
await refHelper.checkCommitInfo(
|
||||
'token',
|
||||
`Merge ${sha1Head} into ${sha1Base}`,
|
||||
repositoryOwner,
|
||||
repositoryName,
|
||||
ref,
|
||||
commit
|
||||
)
|
||||
|
||||
expect(getOctokitSpy).not.toHaveBeenCalled()
|
||||
expect(repoGetSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('matches SHA-256 merge commit info', async () => {
|
||||
const actualHeadSha =
|
||||
'9999999999888888888877777777776666666666555555555544444444443333'
|
||||
setPullRequestContext(sha256Head, sha256Base, sha256Commit)
|
||||
|
||||
await refHelper.checkCommitInfo(
|
||||
'token',
|
||||
`Merge ${actualHeadSha} into ${sha256Base}`,
|
||||
repositoryOwner,
|
||||
repositoryName,
|
||||
ref,
|
||||
sha256Commit
|
||||
)
|
||||
|
||||
expect(getOctokitSpy).toHaveBeenCalledWith(
|
||||
'token',
|
||||
expect.objectContaining({
|
||||
userAgent: expect.stringContaining(
|
||||
`expected_head_sha=${sha256Head};actual_head_sha=${actualHeadSha}`
|
||||
)
|
||||
})
|
||||
)
|
||||
expect(repoGetSpy).toHaveBeenCalledWith({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName
|
||||
})
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
`Expected head sha ${sha256Head}; actual head sha ${actualHeadSha}`
|
||||
)
|
||||
expect(debugSpy).not.toHaveBeenCalledWith('Unexpected message format')
|
||||
})
|
||||
|
||||
it('does not match 50-char hex as a valid merge', async () => {
|
||||
const invalidHeadSha =
|
||||
'99999999998888888888777777777766666666665555555555'
|
||||
setPullRequestContext(sha1Head, sha1Base, commit)
|
||||
|
||||
await refHelper.checkCommitInfo(
|
||||
'token',
|
||||
`Merge ${invalidHeadSha} into ${sha1Base}`,
|
||||
repositoryOwner,
|
||||
repositoryName,
|
||||
ref,
|
||||
commit
|
||||
)
|
||||
|
||||
expect(getOctokitSpy).not.toHaveBeenCalled()
|
||||
expect(repoGetSpy).not.toHaveBeenCalled()
|
||||
expect(debugSpy).toHaveBeenCalledWith('Unexpected message format')
|
||||
})
|
||||
})
|
||||
})
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Verify tags were fetched
|
||||
TAG_COUNT=$(git -C ./fetch-tags-test tag | wc -l)
|
||||
if [ "$TAG_COUNT" -eq 0 ]; then
|
||||
echo "Expected tags to be fetched, but found none"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found $TAG_COUNT tags"
|
||||
@@ -17,7 +17,7 @@ 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
|
||||
git config --local --includes --name-only --get-regexp http.+extraheader && git fetch
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to validate persisted credential"
|
||||
popd
|
||||
|
||||
@@ -17,7 +17,7 @@ fi
|
||||
|
||||
echo "Testing persisted credential"
|
||||
pushd ./submodules-true/submodule-level-1
|
||||
git config --local --name-only --get-regexp http.+extraheader && git fetch
|
||||
git config --local --includes --name-only --get-regexp http.+extraheader && git fetch
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Failed to validate persisted credential"
|
||||
popd
|
||||
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Verify worktree credentials
|
||||
# This test verifies that git credentials work in worktrees created after checkout
|
||||
# Usage: verify-worktree.sh <checkout-path> <worktree-name>
|
||||
|
||||
CHECKOUT_PATH="$1"
|
||||
WORKTREE_NAME="$2"
|
||||
|
||||
if [ -z "$CHECKOUT_PATH" ] || [ -z "$WORKTREE_NAME" ]; then
|
||||
echo "Usage: verify-worktree.sh <checkout-path> <worktree-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$CHECKOUT_PATH"
|
||||
|
||||
# Add safe directory for container environments
|
||||
git config --global --add safe.directory "*" 2>/dev/null || true
|
||||
|
||||
# Show the includeIf configuration
|
||||
echo "Git config includeIf entries:"
|
||||
git config --list --show-origin | grep -i include || true
|
||||
|
||||
# Create the worktree
|
||||
echo "Creating worktree..."
|
||||
git worktree add "../$WORKTREE_NAME" HEAD --detach
|
||||
|
||||
# Change to worktree directory
|
||||
cd "../$WORKTREE_NAME"
|
||||
|
||||
# Verify we're in a worktree
|
||||
echo "Verifying worktree gitdir:"
|
||||
cat .git
|
||||
|
||||
# Verify credentials are available in worktree by checking extraheader is configured
|
||||
echo "Checking credentials in worktree..."
|
||||
if git config --list --show-origin | grep -q "extraheader"; then
|
||||
echo "Credentials are configured in worktree"
|
||||
else
|
||||
echo "ERROR: Credentials are NOT configured in worktree"
|
||||
echo "Full git config:"
|
||||
git config --list --show-origin
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify fetch works in the worktree
|
||||
echo "Fetching in worktree..."
|
||||
git fetch origin
|
||||
|
||||
echo "Worktree credentials test passed!"
|
||||
Reference in new issue
Block a user