mirror of
https://code.forgejo.org/actions/checkout.git
synced 2026-03-28 16:03:07 +00:00
fix: ensure symlinked git directories work
This commit is contained in:
parent
0c366fd6a8
commit
70525c3952
3 changed files with 102 additions and 12 deletions
|
|
@ -238,6 +238,79 @@ describe('git-auth-helper tests', () => {
|
||||||
expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
|
expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const configureAuth_resolvesSymlinksInIncludeIfGitdir =
|
||||||
|
'configureAuth resolves symlinks in includeIf gitdir'
|
||||||
|
it(configureAuth_resolvesSymlinksInIncludeIfGitdir, async () => {
|
||||||
|
if (isWindows) {
|
||||||
|
process.stdout.write(
|
||||||
|
`Skipped test "${configureAuth_resolvesSymlinksInIncludeIfGitdir}". Symlink creation requires admin privileges on Windows.\n`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrange
|
||||||
|
await setup(configureAuth_resolvesSymlinksInIncludeIfGitdir)
|
||||||
|
|
||||||
|
// Create a symlink pointing to the real workspace directory
|
||||||
|
const symlinkPath = path.join(path.dirname(workspace), 'workspace-symlink')
|
||||||
|
await fs.promises.symlink(workspace, symlinkPath)
|
||||||
|
|
||||||
|
// Make git appear to be operating from the symlink path
|
||||||
|
;(git.getWorkingDirectory as jest.Mock).mockReturnValue(symlinkPath)
|
||||||
|
process.env['GITHUB_WORKSPACE'] = symlinkPath
|
||||||
|
|
||||||
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
|
// Assert the host includeIf uses the real resolved path, not the symlink path
|
||||||
|
const localConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
const realGitDir = fs
|
||||||
|
.realpathSync(path.join(symlinkPath, '.git'))
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
const symlinkGitDir = path.join(symlinkPath, '.git').replace(/\\/g, '/')
|
||||||
|
|
||||||
|
expect(realGitDir).not.toBe(symlinkGitDir) // sanity check: paths differ
|
||||||
|
expect(
|
||||||
|
localConfigContent.indexOf(`includeIf.gitdir:${realGitDir}.path`)
|
||||||
|
).toBeGreaterThanOrEqual(0)
|
||||||
|
expect(localConfigContent.indexOf(symlinkGitDir)).toBeLessThan(0)
|
||||||
|
|
||||||
|
// Clean up symlink
|
||||||
|
await fs.promises.unlink(symlinkPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
const configureAuth_fallsBackWhenRealpathSyncFails =
|
||||||
|
'configureAuth falls back to constructed path when realpathSync fails'
|
||||||
|
it(configureAuth_fallsBackWhenRealpathSyncFails, async () => {
|
||||||
|
// Arrange
|
||||||
|
await setup(configureAuth_fallsBackWhenRealpathSyncFails)
|
||||||
|
|
||||||
|
// Use a non-existent path so realpathSync throws ENOENT naturally,
|
||||||
|
// exercising the catch fallback in configureToken()
|
||||||
|
const nonexistentPath = path.join(runnerTemp, 'does-not-exist')
|
||||||
|
;(git.getWorkingDirectory as jest.Mock).mockReturnValue(nonexistentPath)
|
||||||
|
|
||||||
|
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
|
||||||
|
|
||||||
|
// Act - should not throw despite realpathSync failure
|
||||||
|
await authHelper.configureAuth()
|
||||||
|
|
||||||
|
// Assert the fallback constructed path is used in the includeIf entry
|
||||||
|
const localConfigContent = (
|
||||||
|
await fs.promises.readFile(localGitConfigPath)
|
||||||
|
).toString()
|
||||||
|
const fallbackGitDir = path
|
||||||
|
.join(nonexistentPath, '.git')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
expect(
|
||||||
|
localConfigContent.indexOf(`includeIf.gitdir:${fallbackGitDir}.path`)
|
||||||
|
).toBeGreaterThanOrEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
const setsSshCommandEnvVarWhenPersistCredentialsFalse =
|
const setsSshCommandEnvVarWhenPersistCredentialsFalse =
|
||||||
'sets SSH command env var when persist-credentials false'
|
'sets SSH command env var when persist-credentials false'
|
||||||
it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
|
it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
|
||||||
|
|
|
||||||
15
dist/index.js
vendored
15
dist/index.js
vendored
|
|
@ -406,9 +406,18 @@ class GitAuthHelper {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Host git directory
|
// Host git directory - resolve symlinks so includeIf gitdir matching works
|
||||||
let gitDir = path.join(this.git.getWorkingDirectory(), '.git');
|
// on self-hosted runners where _work is a symlink to an external volume.
|
||||||
gitDir = gitDir.replace(/\\/g, '/'); // Use forward slashes, even on Windows
|
let gitDir;
|
||||||
|
try {
|
||||||
|
const constructed = path.join(this.git.getWorkingDirectory(), '.git');
|
||||||
|
gitDir = fs.realpathSync(constructed).replace(/\\/g, '/');
|
||||||
|
}
|
||||||
|
catch (_a) {
|
||||||
|
// Fall back to constructed path if realpathSync fails
|
||||||
|
gitDir = path.join(this.git.getWorkingDirectory(), '.git');
|
||||||
|
gitDir = gitDir.replace(/\\/g, '/');
|
||||||
|
}
|
||||||
// Configure host includeIf
|
// Configure host includeIf
|
||||||
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
|
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`;
|
||||||
yield this.git.config(hostIncludeKey, credentialsConfigPath);
|
yield this.git.config(hostIncludeKey, credentialsConfigPath);
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import * as path from 'path'
|
||||||
import * as regexpHelper from './regexp-helper'
|
import * as regexpHelper from './regexp-helper'
|
||||||
import * as stateHelper from './state-helper'
|
import * as stateHelper from './state-helper'
|
||||||
import * as urlHelper from './url-helper'
|
import * as urlHelper from './url-helper'
|
||||||
import {v4 as uuid} from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import {IGitCommandManager} from './git-command-manager'
|
import { IGitCommandManager } from './git-command-manager'
|
||||||
import {IGitSourceSettings} from './git-source-settings'
|
import { IGitSourceSettings } from './git-source-settings'
|
||||||
|
|
||||||
const IS_WINDOWS = process.platform === 'win32'
|
const IS_WINDOWS = process.platform === 'win32'
|
||||||
const SSH_COMMAND_KEY = 'core.sshCommand'
|
const SSH_COMMAND_KEY = 'core.sshCommand'
|
||||||
|
|
@ -92,7 +92,7 @@ class GitAuthHelper {
|
||||||
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
assert.ok(runnerTemp, 'RUNNER_TEMP is not defined')
|
||||||
const uniqueId = uuid()
|
const uniqueId = uuid()
|
||||||
this.temporaryHomePath = path.join(runnerTemp, uniqueId)
|
this.temporaryHomePath = path.join(runnerTemp, uniqueId)
|
||||||
await fs.promises.mkdir(this.temporaryHomePath, {recursive: true})
|
await fs.promises.mkdir(this.temporaryHomePath, { recursive: true })
|
||||||
|
|
||||||
// Copy the global git config
|
// Copy the global git config
|
||||||
const gitConfigPath = path.join(
|
const gitConfigPath = path.join(
|
||||||
|
|
@ -258,11 +258,11 @@ class GitAuthHelper {
|
||||||
const uniqueId = uuid()
|
const uniqueId = uuid()
|
||||||
this.sshKeyPath = path.join(runnerTemp, uniqueId)
|
this.sshKeyPath = path.join(runnerTemp, uniqueId)
|
||||||
stateHelper.setSshKeyPath(this.sshKeyPath)
|
stateHelper.setSshKeyPath(this.sshKeyPath)
|
||||||
await fs.promises.mkdir(runnerTemp, {recursive: true})
|
await fs.promises.mkdir(runnerTemp, { recursive: true })
|
||||||
await fs.promises.writeFile(
|
await fs.promises.writeFile(
|
||||||
this.sshKeyPath,
|
this.sshKeyPath,
|
||||||
this.settings.sshKey.trim() + '\n',
|
this.settings.sshKey.trim() + '\n',
|
||||||
{mode: 0o600}
|
{ mode: 0o600 }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remove inherited permissions on Windows
|
// Remove inherited permissions on Windows
|
||||||
|
|
@ -366,9 +366,17 @@ class GitAuthHelper {
|
||||||
true // globalConfig?
|
true // globalConfig?
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Host git directory
|
// Host git directory - resolve symlinks so includeIf gitdir matching works
|
||||||
let gitDir = path.join(this.git.getWorkingDirectory(), '.git')
|
// on self-hosted runners where _work is a symlink to an external volume.
|
||||||
gitDir = gitDir.replace(/\\/g, '/') // Use forward slashes, even on Windows
|
let gitDir: string
|
||||||
|
try {
|
||||||
|
const constructed = path.join(this.git.getWorkingDirectory(), '.git')
|
||||||
|
gitDir = fs.realpathSync(constructed).replace(/\\/g, '/')
|
||||||
|
} catch {
|
||||||
|
// Fall back to constructed path if realpathSync fails
|
||||||
|
gitDir = path.join(this.git.getWorkingDirectory(), '.git')
|
||||||
|
gitDir = gitDir.replace(/\\/g, '/')
|
||||||
|
}
|
||||||
|
|
||||||
// Configure host includeIf
|
// Configure host includeIf
|
||||||
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
|
const hostIncludeKey = `includeIf.gitdir:${gitDir}.path`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue