Compare commits

...

6 Commits

Author SHA1 Message Date
c455122937 . 2020-03-19 00:43:38 -04:00
605b7eb961 . 2020-03-19 00:37:38 -04:00
036772999d . 2020-03-19 00:14:00 -04:00
46054cf00b . 2020-03-18 23:34:02 -04:00
85a425b582 use token for workflow repo 2020-03-18 23:33:26 -04:00
9a3a9ade82 persist core.sshCommand for submodules (#184)
* persist core.sshCommand for submodules

* update verbiage; add comments

* fail when submodules or ssh-key and fallback to REST API
2020-03-12 11:42:38 -04:00
11 changed files with 404 additions and 168 deletions

View File

@ -49,19 +49,19 @@ Refer [here](https://github.com/actions/checkout/blob/v1/README.md) for previous
# with the local git config, which enables your scripts to run authenticated git # with the local git config, which enables your scripts to run authenticated git
# commands. The post-job step removes the PAT. # commands. The post-job step removes the PAT.
# #
# We recommend creating a service account with the least permissions necessary. # We recommend using a service account with the least permissions necessary. Also
# Also when generating a new PAT, select the least scopes necessary. # when generating a new PAT, select the least scopes necessary.
# #
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) # [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
# #
# Default: ${{ github.token }} # Default: ${{ github.token }}
token: '' token: ''
# SSH key used to fetch the repository. SSH key is configured with the local git # SSH key used to fetch the repository. The SSH key is configured with the local
# config, which enables your scripts to run authenticated git commands. The # git config, which enables your scripts to run authenticated git commands. The
# post-job step removes the SSH key. # post-job step removes the SSH key.
# #
# We recommend creating a service account with the least permissions necessary. # We recommend using a service account with the least permissions necessary.
# #
# [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets) # [Learn more about creating and using encrypted secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
ssh-key: '' ssh-key: ''

View File

@ -320,6 +320,8 @@ describe('git-auth-helper tests', () => {
).toString() ).toString()
expect(actualSshKeyContent).toBe(settings.sshKey + '\n') expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
if (!isWindows) { if (!isWindows) {
// Assert read/write for user, not group or others.
// Otherwise SSH client will error.
expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe( expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
0o600 0o600
) )
@ -437,14 +439,74 @@ describe('git-auth-helper tests', () => {
} }
) )
const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet = const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet =
'configureSubmoduleAuth configures token when persist credentials true and SSH key not set' 'configureSubmoduleAuth configures submodules when persist credentials false and SSH key not set'
it( it(
configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet, configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet,
async () => { async () => {
// Arrange // Arrange
await setup( await setup(
configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeyNotSet configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet
)
settings.persistCredentials = false
settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toBeCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
/unset-all.*insteadOf/
)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet =
'configureSubmoduleAuth configures submodules when persist credentials false and SSH key set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet,
async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet
)
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
}
)
const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet =
'configureSubmoduleAuth configures submodules when persist credentials true and SSH key not set'
it(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet,
async () => {
// Arrange
await setup(
configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet
) )
settings.sshKey = '' settings.sshKey = ''
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
@ -465,21 +527,21 @@ describe('git-auth-helper tests', () => {
} }
) )
const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet = const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet =
'configureSubmoduleAuth configures token when persist credentials true and SSH key set' 'configureSubmoduleAuth configures submodules when persist credentials true and SSH key set'
it( it(
configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet, configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet,
async () => { async () => {
if (!sshPath) { if (!sshPath) {
process.stdout.write( process.stdout.write(
`Skipped test "${configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n` `Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
) )
return return
} }
// Arrange // Arrange
await setup( await setup(
configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrueAndSshKeySet configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet
) )
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth() await authHelper.configureAuth()
@ -490,96 +552,12 @@ describe('git-auth-helper tests', () => {
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
// Assert // Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2) expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch( expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/ /unset-all.*insteadOf/
) )
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/) expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
} expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
)
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()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toBeCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
/unset-all.*insteadOf/
)
}
)
const configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet =
'configureSubmoduleAuth does not configure URL insteadOf when persist credentials true and SSH key set'
it(
configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet,
async () => {
if (!sshPath) {
process.stdout.write(
`Skipped test "${configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
)
return
}
// Arrange
await setup(
configureSubmoduleAuth_doesNotConfigureUrlInsteadOfWhenPersistCredentialsTrueAndSshKeySet
)
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
/unset-all.*insteadOf/
)
expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
}
)
const configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse =
'configureSubmoduleAuth removes URL insteadOf when persist credentials false'
it(
configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse,
async () => {
// Arrange
await setup(
configureSubmoduleAuth_removesUrlInsteadOfWhenPersistCredentialsFalse
)
settings.persistCredentials = false
const authHelper = gitAuthHelper.createAuthHelper(git, settings)
await authHelper.configureAuth()
const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
mockSubmoduleForeach.mockClear() // reset calls
// Act
await authHelper.configureSubmoduleAuth()
// Assert
expect(mockSubmoduleForeach).toBeCalledTimes(1)
expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
/unset-all.*insteadOf/
)
} }
) )
@ -747,6 +725,7 @@ async function setup(testName: string): Promise<void> {
setEnvironmentVariable: jest.fn((name: string, value: string) => { setEnvironmentVariable: jest.fn((name: string, value: string) => {
git.env[name] = value git.env[name] = value
}), }),
setRemoteUrl: jest.fn(),
submoduleForeach: jest.fn(async () => { submoduleForeach: jest.fn(async () => {
return '' return ''
}), }),
@ -770,7 +749,7 @@ async function setup(testName: string): Promise<void> {
} }
), ),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(), tryGetRemoteUrl: jest.fn(),
tryReset: jest.fn() tryReset: jest.fn()
} }
@ -779,6 +758,7 @@ async function setup(testName: string): Promise<void> {
clean: true, clean: true,
commit: '', commit: '',
fetchDepth: 1, fetchDepth: 1,
isWorkflowRepository: true,
lfs: false, lfs: false,
submodules: false, submodules: false,
nestedSubmodules: false, nestedSubmodules: false,

View File

@ -7,7 +7,8 @@ import {IGitCommandManager} from '../lib/git-command-manager'
const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper') const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper')
let repositoryPath: string let repositoryPath: string
let repositoryUrl: string let httpsUrl: string
let sshUrl: string
let clean: boolean let clean: boolean
let git: IGitCommandManager let git: IGitCommandManager
@ -40,7 +41,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -62,7 +64,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -87,7 +90,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -108,7 +112,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -136,7 +141,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -155,14 +161,15 @@ describe('git-directory-helper tests', () => {
await setup(removesContentsWhenDifferentRepositoryUrl) await setup(removesContentsWhenDifferentRepositoryUrl)
clean = false clean = false
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
const differentRepositoryUrl = const differentRemoteUrl =
'https://github.com/my-different-org/my-different-repo' 'https://github.com/my-different-org/my-different-repo'
// Act // Act
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
differentRepositoryUrl, differentRemoteUrl,
[differentRemoteUrl],
clean clean
) )
@ -186,7 +193,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -211,7 +219,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -235,7 +244,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
undefined, undefined,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -259,7 +269,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -289,7 +300,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -319,7 +331,8 @@ describe('git-directory-helper tests', () => {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
repositoryPath, repositoryPath,
repositoryUrl, httpsUrl,
[httpsUrl, sshUrl],
clean clean
) )
@ -329,6 +342,30 @@ describe('git-directory-helper tests', () => {
expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1') expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1')
expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2') expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2')
}) })
const updatesRemoteUrl = 'updates remote URL'
it(updatesRemoteUrl, async () => {
// Arrange
await setup(updatesRemoteUrl)
await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '')
// Act
await gitDirectoryHelper.prepareExistingDirectory(
git,
repositoryPath,
sshUrl,
[sshUrl, httpsUrl],
clean
)
// Assert
const files = await fs.promises.readdir(repositoryPath)
expect(files.sort()).toEqual(['.git', 'my-file'])
expect(git.isDetached).toHaveBeenCalled()
expect(git.branchList).toHaveBeenCalled()
expect(core.warning).not.toHaveBeenCalled()
expect(git.setRemoteUrl).toHaveBeenCalledWith(sshUrl)
})
}) })
async function setup(testName: string): Promise<void> { async function setup(testName: string): Promise<void> {
@ -338,8 +375,9 @@ async function setup(testName: string): Promise<void> {
repositoryPath = path.join(testWorkspace, testName) repositoryPath = path.join(testWorkspace, testName)
await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true}) await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true})
// Repository URL // Remote URLs
repositoryUrl = 'https://github.com/my-org/my-repo' httpsUrl = 'https://github.com/my-org/my-repo'
sshUrl = 'git@github.com:my-org/my-repo'
// Clean // Clean
clean = true clean = true
@ -365,6 +403,7 @@ async function setup(testName: string): Promise<void> {
remoteAdd: jest.fn(), remoteAdd: jest.fn(),
removeEnvironmentVariable: jest.fn(), removeEnvironmentVariable: jest.fn(),
setEnvironmentVariable: jest.fn(), setEnvironmentVariable: jest.fn(),
setRemoteUrl: jest.fn(),
submoduleForeach: jest.fn(), submoduleForeach: jest.fn(),
submoduleSync: jest.fn(), submoduleSync: jest.fn(),
submoduleUpdate: jest.fn(), submoduleUpdate: jest.fn(),
@ -374,10 +413,10 @@ async function setup(testName: string): Promise<void> {
}), }),
tryConfigUnset: jest.fn(), tryConfigUnset: jest.fn(),
tryDisableAutomaticGarbageCollection: jest.fn(), tryDisableAutomaticGarbageCollection: jest.fn(),
tryGetFetchUrl: jest.fn(async () => { tryGetRemoteUrl: jest.fn(async () => {
// Sanity check - this function shouldn't be called when the .git directory doesn't exist // Sanity check - this function shouldn't be called when the .git directory doesn't exist
await fs.promises.stat(path.join(repositoryPath, '.git')) await fs.promises.stat(path.join(repositoryPath, '.git'))
return repositoryUrl return httpsUrl
}), }),
tryReset: jest.fn(async () => { tryReset: jest.fn(async () => {
return true return true

View File

@ -16,7 +16,7 @@ inputs:
commands. The post-job step removes the PAT. commands. The post-job step removes the PAT.
We recommend creating a service account with the least permissions necessary. We recommend using a service account with the least permissions necessary.
Also when generating a new PAT, select the least scopes necessary. Also when generating a new PAT, select the least scopes necessary.
@ -24,12 +24,12 @@ inputs:
default: ${{ github.token }} default: ${{ github.token }}
ssh-key: ssh-key:
description: > description: >
SSH key used to fetch the repository. SSH key is configured with the local SSH key used to fetch the repository. The SSH key is configured with the local
git config, which enables your scripts to run authenticated git commands. git config, which enables your scripts to run authenticated git commands.
The post-job step removes the SSH key. The post-job step removes the SSH key.
We recommend creating a service account with the least permissions necessary. We recommend using a service account with the least permissions necessary.
[Learn more about creating and using [Learn more about creating and using

109
dist/index.js vendored
View File

@ -5122,6 +5122,7 @@ class GitAuthHelper {
this.tokenConfigKey = `http.https://${HOSTNAME}/.extraheader`; this.tokenConfigKey = `http.https://${HOSTNAME}/.extraheader`;
this.insteadOfKey = `url.https://${HOSTNAME}/.insteadOf`; this.insteadOfKey = `url.https://${HOSTNAME}/.insteadOf`;
this.insteadOfValue = `git@${HOSTNAME}:`; this.insteadOfValue = `git@${HOSTNAME}:`;
this.sshCommand = '';
this.sshKeyPath = ''; this.sshKeyPath = '';
this.sshKnownHostsPath = ''; this.sshKnownHostsPath = '';
this.temporaryHomePath = ''; this.temporaryHomePath = '';
@ -5205,8 +5206,12 @@ class GitAuthHelper {
core.debug(`Replacing token placeholder in '${configPath}'`); core.debug(`Replacing token placeholder in '${configPath}'`);
this.replaceTokenPlaceholder(configPath); this.replaceTokenPlaceholder(configPath);
} }
// Configure HTTPS instead of SSH if (this.settings.sshKey) {
if (!this.settings.sshKey) { // Configure core.sshCommand
yield this.git.submoduleForeach(`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`, this.settings.nestedSubmodules);
}
else {
// Configure HTTPS instead of SSH
yield this.git.submoduleForeach(`git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`, this.settings.nestedSubmodules); yield this.git.submoduleForeach(`git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`, this.settings.nestedSubmodules);
} }
} }
@ -5220,7 +5225,7 @@ class GitAuthHelper {
} }
removeGlobalAuth() { removeGlobalAuth() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
core.info(`Unsetting HOME override`); core.debug(`Unsetting HOME override`);
this.git.removeEnvironmentVariable('HOME'); this.git.removeEnvironmentVariable('HOME');
yield io.rmRF(this.temporaryHomePath); yield io.rmRF(this.temporaryHomePath);
}); });
@ -5268,16 +5273,16 @@ class GitAuthHelper {
yield fs.promises.writeFile(this.sshKnownHostsPath, knownHosts); yield fs.promises.writeFile(this.sshKnownHostsPath, knownHosts);
// Configure GIT_SSH_COMMAND // Configure GIT_SSH_COMMAND
const sshPath = yield io.which('ssh', true); const sshPath = yield io.which('ssh', true);
let sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"`; this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(this.sshKeyPath)}"`;
if (this.settings.sshStrict) { if (this.settings.sshStrict) {
sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no'; this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no';
} }
sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(this.sshKnownHostsPath)}"`; this.sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(this.sshKnownHostsPath)}"`;
core.info(`Temporarily overriding GIT_SSH_COMMAND=${sshCommand}`); core.info(`Temporarily overriding GIT_SSH_COMMAND=${this.sshCommand}`);
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', sshCommand); this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand);
// Configure core.sshCommand // Configure core.sshCommand
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
yield this.git.config(SSH_COMMAND_KEY, sshCommand); yield this.git.config(SSH_COMMAND_KEY, this.sshCommand);
} }
}); });
} }
@ -5576,6 +5581,11 @@ class GitCommandManager {
setEnvironmentVariable(name, value) { setEnvironmentVariable(name, value) {
this.gitEnv[name] = value; this.gitEnv[name] = value;
} }
setRemoteUrl(value) {
return __awaiter(this, void 0, void 0, function* () {
yield this.config('git.remote.url', value);
});
}
submoduleForeach(command, recursive) { submoduleForeach(command, recursive) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const args = ['submodule', 'foreach']; const args = ['submodule', 'foreach'];
@ -5638,7 +5648,7 @@ class GitCommandManager {
return output.exitCode === 0; return output.exitCode === 0;
}); });
} }
tryGetFetchUrl() { tryGetRemoteUrl() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const output = yield this.execGit(['config', '--local', '--get', 'remote.origin.url'], true); const output = yield this.execGit(['config', '--local', '--get', 'remote.origin.url'], true);
if (output.exitCode !== 0) { if (output.exitCode !== 0) {
@ -5795,11 +5805,12 @@ const stateHelper = __importStar(__webpack_require__(153));
const hostname = 'github.com'; const hostname = 'github.com';
function getSource(settings) { function getSource(settings) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
// Repository URL
core.info(`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`); core.info(`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`);
const repositoryUrl = settings.sshKey // Remote URL
? `git@${hostname}:${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}.git` const httpsUrl = `https://${hostname}/${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}`;
: `https://${hostname}/${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}`; const sshUrl = `git@${hostname}:${encodeURIComponent(settings.repositoryOwner)}/${encodeURIComponent(settings.repositoryName)}.git`;
// Always fetch the workflow repository using the token, not the SSH key
const initialRemoteUrl = !settings.sshKey || settings.isWorkflowRepository ? httpsUrl : sshUrl;
// Remove conflicting file path // Remove conflicting file path
if (fsHelper.fileExistsSync(settings.repositoryPath)) { if (fsHelper.fileExistsSync(settings.repositoryPath)) {
yield io.rmRF(settings.repositoryPath); yield io.rmRF(settings.repositoryPath);
@ -5811,15 +5822,23 @@ function getSource(settings) {
yield io.mkdirP(settings.repositoryPath); yield io.mkdirP(settings.repositoryPath);
} }
// Git command manager // Git command manager
core.startGroup('Getting Git version info');
const git = yield getGitCommandManager(settings); const git = yield getGitCommandManager(settings);
core.endGroup();
// Prepare existing directory, otherwise recreate // Prepare existing directory, otherwise recreate
if (isExisting) { if (isExisting) {
yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, repositoryUrl, settings.clean); yield gitDirectoryHelper.prepareExistingDirectory(git, settings.repositoryPath, initialRemoteUrl, [httpsUrl, sshUrl], settings.clean);
} }
if (!git) { if (!git) {
// Downloading using REST API // Downloading using REST API
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`);
if (settings.submodules) {
throw new Error(`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`);
}
else if (settings.sshKey) {
throw new Error(`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. 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; return;
} }
@ -5827,46 +5846,72 @@ function getSource(settings) {
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'))) {
core.startGroup('Initializing the repository');
yield git.init(); yield git.init();
yield git.remoteAdd('origin', repositoryUrl); yield git.remoteAdd('origin', initialRemoteUrl);
core.endGroup();
} }
// Disable automatic garbage collection // Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection');
if (!(yield git.tryDisableAutomaticGarbageCollection())) { 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.`); core.warning(`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`);
} }
core.endGroup();
const authHelper = gitAuthHelper.createAuthHelper(git, settings); const authHelper = gitAuthHelper.createAuthHelper(git, settings);
try { try {
// Configure auth // Configure auth
core.startGroup('Setting up auth');
yield authHelper.configureAuth(); yield authHelper.configureAuth();
core.endGroup();
// LFS install // LFS install
if (settings.lfs) { if (settings.lfs) {
yield git.lfsInstall(); yield git.lfsInstall();
} }
// Fetch // Fetch
core.startGroup('Fetching the repository');
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit); const refSpec = refHelper.getRefSpec(settings.ref, settings.commit);
yield git.fetch(settings.fetchDepth, refSpec); yield git.fetch(settings.fetchDepth, refSpec);
core.endGroup();
// Checkout info // Checkout info
core.startGroup('Determining the checkout info');
const checkoutInfo = yield refHelper.getCheckoutInfo(git, settings.ref, settings.commit); const checkoutInfo = yield refHelper.getCheckoutInfo(git, settings.ref, settings.commit);
core.endGroup();
// LFS fetch // LFS fetch
// Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
// Explicit lfs fetch will fetch lfs objects in parallel. // Explicit lfs fetch will fetch lfs objects in parallel.
if (settings.lfs) { if (settings.lfs) {
core.startGroup('Fetching LFS objects');
yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref); yield git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref);
core.endGroup();
}
// Fix URL when using SSH
if (settings.sshKey && initialRemoteUrl !== sshUrl) {
core.startGroup('Updating the remote URL');
yield git.setRemoteUrl(sshUrl);
core.endGroup();
} }
// Checkout // Checkout
core.startGroup('Checking out the ref');
yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint); yield git.checkout(checkoutInfo.ref, checkoutInfo.startPoint);
core.endGroup();
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try { try {
// Temporarily override global config // Temporarily override global config
core.startGroup('Setting up auth for fetching submodules');
yield authHelper.configureGlobalAuth(); yield authHelper.configureGlobalAuth();
core.endGroup();
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules');
yield git.submoduleSync(settings.nestedSubmodules); yield git.submoduleSync(settings.nestedSubmodules);
yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules); yield git.submoduleUpdate(settings.fetchDepth, settings.nestedSubmodules);
yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules); yield git.submoduleForeach('git config --local gc.auto 0', settings.nestedSubmodules);
core.endGroup();
// Persist credentials // Persist credentials
if (settings.persistCredentials) { if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules');
yield authHelper.configureSubmoduleAuth(); yield authHelper.configureSubmoduleAuth();
core.endGroup();
} }
} }
finally { finally {
@ -5880,7 +5925,9 @@ function getSource(settings) {
finally { finally {
// Remove auth // Remove auth
if (!settings.persistCredentials) { if (!settings.persistCredentials) {
core.startGroup('Removing auth');
yield authHelper.removeAuth(); yield authHelper.removeAuth();
core.endGroup();
} }
} }
}); });
@ -7180,21 +7227,29 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result; return result;
}; };
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 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));
function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) { function prepareExistingDirectory(git, repositoryPath, preferredRemoteUrl, allowedRemoteUrls, clean) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined');
assert.ok(preferredRemoteUrl, 'Expected preferredRemoteUrl to be defined');
assert.ok(allowedRemoteUrls, 'Expected allowedRemoteUrls to be defined');
assert.ok(allowedRemoteUrls.length, 'Expected allowedRemoteUrls to have at least one value');
// Indicates whether to delete the directory contents
let remove = false; let remove = false;
// The remote URL
let remoteUrl;
// Check whether using git or REST API // Check whether using git or REST API
if (!git) { if (!git) {
remove = true; remove = true;
} }
// Fetch URL does not match // Fetch URL does not match
else if (!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) || else if (!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
repositoryUrl !== (yield git.tryGetFetchUrl())) { allowedRemoteUrls.indexOf((remoteUrl = yield git.tryGetRemoteUrl())) < 0) {
remove = true; remove = true;
} }
else { else {
@ -7212,6 +7267,7 @@ function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
} }
} }
try { try {
core.startGroup('Removing previously created refs, to avoid conflicts');
// Checkout detached HEAD // Checkout detached HEAD
if (!(yield git.isDetached())) { if (!(yield git.isDetached())) {
yield git.checkoutDetach(); yield git.checkoutDetach();
@ -7226,8 +7282,10 @@ function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
for (const branch of branches) { for (const branch of branches) {
yield git.branchDelete(true, branch); yield git.branchDelete(true, branch);
} }
core.endGroup();
// Clean // Clean
if (clean) { if (clean) {
core.startGroup('Cleaning the repository');
if (!(yield git.tryClean())) { if (!(yield git.tryClean())) {
core.debug(`The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`); core.debug(`The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`);
remove = true; remove = true;
@ -7235,10 +7293,17 @@ function prepareExistingDirectory(git, repositoryPath, repositoryUrl, clean) {
else if (!(yield git.tryReset())) { else if (!(yield git.tryReset())) {
remove = true; remove = true;
} }
core.endGroup();
if (remove) { if (remove) {
core.warning(`Unable to clean or reset the repository. The repository will be recreated instead.`); core.warning(`Unable to clean or reset the repository. The repository will be recreated instead.`);
} }
} }
// Update to the preferred remote URL
if (remoteUrl !== preferredRemoteUrl) {
core.startGroup('Updating the remote URL');
yield git.setRemoteUrl(preferredRemoteUrl);
core.endGroup();
}
} }
catch (error) { catch (error) {
core.warning(`Unable to prepare the existing repository. The repository will be recreated instead.`); core.warning(`Unable to prepare the existing repository. The repository will be recreated instead.`);
@ -13978,6 +14043,7 @@ const core = __importStar(__webpack_require__(470));
const fsHelper = __importStar(__webpack_require__(618)); const fsHelper = __importStar(__webpack_require__(618));
const github = __importStar(__webpack_require__(469)); const github = __importStar(__webpack_require__(469));
const path = __importStar(__webpack_require__(622)); const path = __importStar(__webpack_require__(622));
const hostname = 'github.com';
function getInputs() { function getInputs() {
const result = {}; const result = {};
// GitHub workspace // GitHub workspace
@ -14007,12 +14073,13 @@ function getInputs() {
throw new Error(`Repository path '${result.repositoryPath}' is not under '${githubWorkspacePath}'`); throw new Error(`Repository path '${result.repositoryPath}' is not under '${githubWorkspacePath}'`);
} }
// Workflow repository? // Workflow repository?
const isWorkflowRepository = qualifiedRepository.toUpperCase() === result.isWorkflowRepository =
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase(); qualifiedRepository.toUpperCase() ===
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase();
// Source branch, source version // Source branch, source version
result.ref = core.getInput('ref'); result.ref = core.getInput('ref');
if (!result.ref) { if (!result.ref) {
if (isWorkflowRepository) { if (result.isWorkflowRepository) {
result.ref = github.context.ref; result.ref = github.context.ref;
result.commit = github.context.sha; result.commit = github.context.sha;
// Some events have an unqualifed ref. For example when a PR is merged (pull_request closed event), // Some events have an unqualifed ref. For example when a PR is merged (pull_request closed event),

View File

@ -37,6 +37,7 @@ class GitAuthHelper {
private readonly tokenPlaceholderConfigValue: string private readonly tokenPlaceholderConfigValue: string
private readonly insteadOfKey: string = `url.https://${HOSTNAME}/.insteadOf` private readonly insteadOfKey: string = `url.https://${HOSTNAME}/.insteadOf`
private readonly insteadOfValue: string = `git@${HOSTNAME}:` private readonly insteadOfValue: string = `git@${HOSTNAME}:`
private sshCommand = ''
private sshKeyPath = '' private sshKeyPath = ''
private sshKnownHostsPath = '' private sshKnownHostsPath = ''
private temporaryHomePath = '' private temporaryHomePath = ''
@ -144,8 +145,14 @@ class GitAuthHelper {
this.replaceTokenPlaceholder(configPath) this.replaceTokenPlaceholder(configPath)
} }
// Configure HTTPS instead of SSH if (this.settings.sshKey) {
if (!this.settings.sshKey) { // Configure core.sshCommand
await this.git.submoduleForeach(
`git config --local '${SSH_COMMAND_KEY}' '${this.sshCommand}'`,
this.settings.nestedSubmodules
)
} else {
// Configure HTTPS instead of SSH
await this.git.submoduleForeach( await this.git.submoduleForeach(
`git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`, `git config --local '${this.insteadOfKey}' '${this.insteadOfValue}'`,
this.settings.nestedSubmodules this.settings.nestedSubmodules
@ -160,7 +167,7 @@ class GitAuthHelper {
} }
async removeGlobalAuth(): Promise<void> { async removeGlobalAuth(): Promise<void> {
core.info(`Unsetting HOME override`) core.debug(`Unsetting HOME override`)
this.git.removeEnvironmentVariable('HOME') this.git.removeEnvironmentVariable('HOME')
await io.rmRF(this.temporaryHomePath) await io.rmRF(this.temporaryHomePath)
} }
@ -218,21 +225,21 @@ class GitAuthHelper {
// Configure GIT_SSH_COMMAND // Configure GIT_SSH_COMMAND
const sshPath = await io.which('ssh', true) const sshPath = await io.which('ssh', true)
let sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename( this.sshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
this.sshKeyPath this.sshKeyPath
)}"` )}"`
if (this.settings.sshStrict) { if (this.settings.sshStrict) {
sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no' this.sshCommand += ' -o StrictHostKeyChecking=yes -o CheckHostIP=no'
} }
sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename( this.sshCommand += ` -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
this.sshKnownHostsPath this.sshKnownHostsPath
)}"` )}"`
core.info(`Temporarily overriding GIT_SSH_COMMAND=${sshCommand}`) core.info(`Temporarily overriding GIT_SSH_COMMAND=${this.sshCommand}`)
this.git.setEnvironmentVariable('GIT_SSH_COMMAND', sshCommand) this.git.setEnvironmentVariable('GIT_SSH_COMMAND', this.sshCommand)
// Configure core.sshCommand // Configure core.sshCommand
if (this.settings.persistCredentials) { if (this.settings.persistCredentials) {
await this.git.config(SSH_COMMAND_KEY, sshCommand) await this.git.config(SSH_COMMAND_KEY, this.sshCommand)
} }
} }

View File

@ -33,6 +33,7 @@ export interface IGitCommandManager {
remoteAdd(remoteName: string, remoteUrl: string): Promise<void> remoteAdd(remoteName: string, remoteUrl: string): Promise<void>
removeEnvironmentVariable(name: string): void removeEnvironmentVariable(name: string): void
setEnvironmentVariable(name: string, value: string): void setEnvironmentVariable(name: string, value: string): void
setRemoteUrl(url: string): Promise<void>
submoduleForeach(command: string, recursive: boolean): Promise<string> submoduleForeach(command: string, recursive: boolean): Promise<string>
submoduleSync(recursive: boolean): Promise<void> submoduleSync(recursive: boolean): Promise<void>
submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void> submoduleUpdate(fetchDepth: number, recursive: boolean): Promise<void>
@ -40,7 +41,7 @@ export interface IGitCommandManager {
tryClean(): Promise<boolean> tryClean(): Promise<boolean>
tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean> tryConfigUnset(configKey: string, globalConfig?: boolean): Promise<boolean>
tryDisableAutomaticGarbageCollection(): Promise<boolean> tryDisableAutomaticGarbageCollection(): Promise<boolean>
tryGetFetchUrl(): Promise<string> tryGetRemoteUrl(): Promise<string>
tryReset(): Promise<boolean> tryReset(): Promise<boolean>
} }
@ -241,6 +242,10 @@ class GitCommandManager {
this.gitEnv[name] = value this.gitEnv[name] = value
} }
async setRemoteUrl(value: string): Promise<void> {
await this.config('git.remote.url', value)
}
async submoduleForeach(command: string, recursive: boolean): Promise<string> { async submoduleForeach(command: string, recursive: boolean): Promise<string> {
const args = ['submodule', 'foreach'] const args = ['submodule', 'foreach']
if (recursive) { if (recursive) {
@ -309,7 +314,7 @@ class GitCommandManager {
return output.exitCode === 0 return output.exitCode === 0
} }
async tryGetFetchUrl(): Promise<string> { async tryGetRemoteUrl(): Promise<string> {
const output = await this.execGit( const output = await this.execGit(
['config', '--local', '--get', 'remote.origin.url'], ['config', '--local', '--get', 'remote.origin.url'],
true true

View File

@ -1,18 +1,33 @@
import * as assert from 'assert'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as fs from 'fs' import * as fs from 'fs'
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 {IGitCommandManager} from './git-command-manager' import {IGitCommandManager} from './git-command-manager'
import {IGitSourceSettings} from './git-source-settings'
export async function prepareExistingDirectory( export async function prepareExistingDirectory(
git: IGitCommandManager | undefined, git: IGitCommandManager | undefined,
repositoryPath: string, repositoryPath: string,
repositoryUrl: string, preferredRemoteUrl: string,
allowedRemoteUrls: string[],
clean: boolean clean: boolean
): Promise<void> { ): Promise<void> {
assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
assert.ok(preferredRemoteUrl, 'Expected preferredRemoteUrl to be defined')
assert.ok(allowedRemoteUrls, 'Expected allowedRemoteUrls to be defined')
assert.ok(
allowedRemoteUrls.length,
'Expected allowedRemoteUrls to have at least one value'
)
// Indicates whether to delete the directory contents
let remove = false let remove = false
// The remote URL
let remoteUrl: string
// Check whether using git or REST API // Check whether using git or REST API
if (!git) { if (!git) {
remove = true remove = true
@ -20,7 +35,7 @@ export async function prepareExistingDirectory(
// Fetch URL does not match // Fetch URL does not match
else if ( else if (
!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) || !fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
repositoryUrl !== (await git.tryGetFetchUrl()) allowedRemoteUrls.indexOf((remoteUrl = await git.tryGetRemoteUrl())) < 0
) { ) {
remove = true remove = true
} else { } else {
@ -38,6 +53,7 @@ export async function prepareExistingDirectory(
} }
try { try {
core.startGroup('Removing previously created refs, to avoid conflicts')
// Checkout detached HEAD // Checkout detached HEAD
if (!(await git.isDetached())) { if (!(await git.isDetached())) {
await git.checkoutDetach() await git.checkoutDetach()
@ -54,9 +70,11 @@ export async function prepareExistingDirectory(
for (const branch of branches) { for (const branch of branches) {
await git.branchDelete(true, branch) await git.branchDelete(true, branch)
} }
core.endGroup()
// Clean // Clean
if (clean) { if (clean) {
core.startGroup('Cleaning the repository')
if (!(await git.tryClean())) { if (!(await git.tryClean())) {
core.debug( core.debug(
`The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.` `The clean command failed. This might be caused by: 1) path too long, 2) permission issue, or 3) file in use. For futher investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
@ -65,6 +83,7 @@ export async function prepareExistingDirectory(
} else if (!(await git.tryReset())) { } else if (!(await git.tryReset())) {
remove = true remove = true
} }
core.endGroup()
if (remove) { if (remove) {
core.warning( core.warning(
@ -72,6 +91,13 @@ export async function prepareExistingDirectory(
) )
} }
} }
// Update to the preferred remote URL
if (remoteUrl !== preferredRemoteUrl) {
core.startGroup('Updating the remote URL')
await git.setRemoteUrl(preferredRemoteUrl)
core.endGroup()
}
} catch (error) { } catch (error) {
core.warning( core.warning(
`Unable to prepare the existing repository. The repository will be recreated instead.` `Unable to prepare the existing repository. The repository will be recreated instead.`

View File

@ -14,17 +14,21 @@ import {IGitSourceSettings} from './git-source-settings'
const hostname = 'github.com' const hostname = 'github.com'
export async function getSource(settings: IGitSourceSettings): Promise<void> { export async function getSource(settings: IGitSourceSettings): Promise<void> {
// Repository URL
core.info( core.info(
`Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}` `Syncing repository: ${settings.repositoryOwner}/${settings.repositoryName}`
) )
const repositoryUrl = settings.sshKey
? `git@${hostname}:${encodeURIComponent( // Remote URL
settings.repositoryOwner const httpsUrl = `https://${hostname}/${encodeURIComponent(
)}/${encodeURIComponent(settings.repositoryName)}.git` settings.repositoryOwner
: `https://${hostname}/${encodeURIComponent( )}/${encodeURIComponent(settings.repositoryName)}`
settings.repositoryOwner const sshUrl = `git@${hostname}:${encodeURIComponent(
)}/${encodeURIComponent(settings.repositoryName)}` settings.repositoryOwner
)}/${encodeURIComponent(settings.repositoryName)}.git`
// Always fetch the workflow repository using the token, not the SSH key
const initialRemoteUrl =
!settings.sshKey || settings.isWorkflowRepository ? httpsUrl : sshUrl
// Remove conflicting file path // Remove conflicting file path
if (fsHelper.fileExistsSync(settings.repositoryPath)) { if (fsHelper.fileExistsSync(settings.repositoryPath)) {
@ -39,14 +43,17 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
// Git command manager // Git command manager
core.startGroup('Getting Git version info')
const git = await getGitCommandManager(settings) const git = await getGitCommandManager(settings)
core.endGroup()
// Prepare existing directory, otherwise recreate // Prepare existing directory, otherwise recreate
if (isExisting) { if (isExisting) {
await gitDirectoryHelper.prepareExistingDirectory( await gitDirectoryHelper.prepareExistingDirectory(
git, git,
settings.repositoryPath, settings.repositoryPath,
repositoryUrl, initialRemoteUrl,
[httpsUrl, sshUrl],
settings.clean settings.clean
) )
} }
@ -57,6 +64,16 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
core.info( core.info(
`To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH` `To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH`
) )
if (settings.submodules) {
throw new Error(
`Input 'submodules' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
)
} else if (settings.sshKey) {
throw new Error(
`Input 'ssh-key' not supported when falling back to download using the GitHub REST API. To create a local Git repository instead, add Git ${gitCommandManager.MinimumGitVersion} or higher to the PATH.`
)
}
await githubApiHelper.downloadRepository( await githubApiHelper.downloadRepository(
settings.authToken, settings.authToken,
settings.repositoryOwner, settings.repositoryOwner,
@ -75,21 +92,27 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
if ( if (
!fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git')) !fsHelper.directoryExistsSync(path.join(settings.repositoryPath, '.git'))
) { ) {
core.startGroup('Initializing the repository')
await git.init() await git.init()
await git.remoteAdd('origin', repositoryUrl) await git.remoteAdd('origin', initialRemoteUrl)
core.endGroup()
} }
// Disable automatic garbage collection // Disable automatic garbage collection
core.startGroup('Disabling automatic garbage collection')
if (!(await git.tryDisableAutomaticGarbageCollection())) { if (!(await git.tryDisableAutomaticGarbageCollection())) {
core.warning( core.warning(
`Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.` `Unable to turn off git automatic garbage collection. The git fetch operation may trigger garbage collection and cause a delay.`
) )
} }
core.endGroup()
const authHelper = gitAuthHelper.createAuthHelper(git, settings) const authHelper = gitAuthHelper.createAuthHelper(git, settings)
try { try {
// Configure auth // Configure auth
core.startGroup('Setting up auth')
await authHelper.configureAuth() await authHelper.configureAuth()
core.endGroup()
// LFS install // LFS install
if (settings.lfs) { if (settings.lfs) {
@ -97,33 +120,51 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} }
// Fetch // Fetch
core.startGroup('Fetching the repository')
const refSpec = refHelper.getRefSpec(settings.ref, settings.commit) const refSpec = refHelper.getRefSpec(settings.ref, settings.commit)
await git.fetch(settings.fetchDepth, refSpec) await git.fetch(settings.fetchDepth, refSpec)
core.endGroup()
// Checkout info // Checkout info
core.startGroup('Determining the checkout info')
const checkoutInfo = await refHelper.getCheckoutInfo( const checkoutInfo = await refHelper.getCheckoutInfo(
git, git,
settings.ref, settings.ref,
settings.commit settings.commit
) )
core.endGroup()
// LFS fetch // LFS fetch
// Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time). // Explicit lfs-fetch to avoid slow checkout (fetches one lfs object at a time).
// Explicit lfs fetch will fetch lfs objects in parallel. // Explicit lfs fetch will fetch lfs objects in parallel.
if (settings.lfs) { if (settings.lfs) {
core.startGroup('Fetching LFS objects')
await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref) await git.lfsFetch(checkoutInfo.startPoint || checkoutInfo.ref)
core.endGroup()
}
// Fix URL when using SSH
if (settings.sshKey && initialRemoteUrl !== sshUrl) {
core.startGroup('Updating the remote URL')
await git.setRemoteUrl(sshUrl)
core.endGroup()
} }
// Checkout // Checkout
core.startGroup('Checking out the ref')
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint) await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
core.endGroup()
// Submodules // Submodules
if (settings.submodules) { if (settings.submodules) {
try { try {
// Temporarily override global config // Temporarily override global config
core.startGroup('Setting up auth for fetching submodules')
await authHelper.configureGlobalAuth() await authHelper.configureGlobalAuth()
core.endGroup()
// Checkout submodules // Checkout submodules
core.startGroup('Fetching submodules')
await git.submoduleSync(settings.nestedSubmodules) await git.submoduleSync(settings.nestedSubmodules)
await git.submoduleUpdate( await git.submoduleUpdate(
settings.fetchDepth, settings.fetchDepth,
@ -133,10 +174,13 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
'git config --local gc.auto 0', 'git config --local gc.auto 0',
settings.nestedSubmodules settings.nestedSubmodules
) )
core.endGroup()
// Persist credentials // Persist credentials
if (settings.persistCredentials) { if (settings.persistCredentials) {
core.startGroup('Persisting credentials for submodules')
await authHelper.configureSubmoduleAuth() await authHelper.configureSubmoduleAuth()
core.endGroup()
} }
} finally { } finally {
// Remove temporary global config override // Remove temporary global config override
@ -149,7 +193,9 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
} finally { } finally {
// Remove auth // Remove auth
if (!settings.persistCredentials) { if (!settings.persistCredentials) {
core.startGroup('Removing auth')
await authHelper.removeAuth() await authHelper.removeAuth()
core.endGroup()
} }
} }
} }

View File

@ -1,17 +1,81 @@
export interface IGitSourceSettings { export interface IGitSourceSettings {
/**
* The location on disk where the repository will be placed
*/
repositoryPath: string repositoryPath: string
/**
* The repository owner
*/
repositoryOwner: string repositoryOwner: string
/**
* The repository name
*/
repositoryName: string repositoryName: string
/**
* Indicates whether the repository is main workflow repository
*/
isWorkflowRepository: boolean
/**
* The ref to fetch
*/
ref: string ref: string
/**
* The commit to checkout
*/
commit: string commit: string
/**
* Indicates whether to clean the repository
*/
clean: boolean clean: boolean
/**
* The depth when fetching
*/
fetchDepth: number fetchDepth: number
/**
* Indicates whether to fetch LFS objects
*/
lfs: boolean lfs: boolean
/**
* Indicates whether to checkout submodules
*/
submodules: boolean submodules: boolean
/**
* Indicates whether to recursively checkout submodules
*/
nestedSubmodules: boolean nestedSubmodules: boolean
/**
* The auth token to use when fetching the repository
*/
authToken: string authToken: string
/**
* The SSH key to configure
*/
sshKey: string sshKey: string
/**
* Additional SSH known hosts
*/
sshKnownHosts: string sshKnownHosts: string
/**
* Indicates whether the server must be a known host
*/
sshStrict: boolean sshStrict: boolean
/**
* Indicates whether to persist the credentials on disk to enable scripting authenticated git commands
*/
persistCredentials: boolean persistCredentials: boolean
} }

View File

@ -4,6 +4,8 @@ import * as github from '@actions/github'
import * as path from 'path' import * as path from 'path'
import {IGitSourceSettings} from './git-source-settings' import {IGitSourceSettings} from './git-source-settings'
const hostname = 'github.com'
export function getInputs(): IGitSourceSettings { export function getInputs(): IGitSourceSettings {
const result = ({} as unknown) as IGitSourceSettings const result = ({} as unknown) as IGitSourceSettings
@ -51,14 +53,14 @@ export function getInputs(): IGitSourceSettings {
} }
// Workflow repository? // Workflow repository?
const isWorkflowRepository = result.isWorkflowRepository =
qualifiedRepository.toUpperCase() === qualifiedRepository.toUpperCase() ===
`${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase() `${github.context.repo.owner}/${github.context.repo.repo}`.toUpperCase()
// Source branch, source version // Source branch, source version
result.ref = core.getInput('ref') result.ref = core.getInput('ref')
if (!result.ref) { if (!result.ref) {
if (isWorkflowRepository) { if (result.isWorkflowRepository) {
result.ref = github.context.ref result.ref = github.context.ref
result.commit = github.context.sha result.commit = github.context.sha