mirror of
https://github.com/actions/checkout.git
synced 2025-08-14 18:15:08 +00:00
Makes preserve-local-changes option work consistently in all scenarios, including when repository URL changes. Updates warning message to correctly reflect this behavior.
165 lines
5.5 KiB
TypeScript
165 lines
5.5 KiB
TypeScript
import * as assert from 'assert'
|
|
import * as core from '@actions/core'
|
|
import * as fs from 'fs'
|
|
import * as fsHelper from './fs-helper'
|
|
import * as io from '@actions/io'
|
|
import * as path from 'path'
|
|
import {IGitCommandManager} from './git-command-manager'
|
|
|
|
export async function prepareExistingDirectory(
|
|
git: IGitCommandManager | undefined,
|
|
repositoryPath: string,
|
|
repositoryUrl: string,
|
|
clean: boolean,
|
|
ref: string,
|
|
preserveLocalChanges: boolean = false
|
|
): Promise<void> {
|
|
assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
|
|
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined')
|
|
|
|
// Indicates whether to delete the directory contents
|
|
let remove = false
|
|
|
|
// If preserveLocalChanges is true, log it
|
|
if (preserveLocalChanges) {
|
|
core.info(
|
|
`Preserve local changes is enabled, will attempt to keep local files`
|
|
)
|
|
}
|
|
|
|
// Check whether using git or REST API
|
|
if (!git) {
|
|
remove = true
|
|
}
|
|
// Fetch URL does not match
|
|
else if (
|
|
!fsHelper.directoryExistsSync(path.join(repositoryPath, '.git')) ||
|
|
repositoryUrl !== (await git.tryGetFetchUrl())
|
|
) {
|
|
remove = true
|
|
} else {
|
|
// Delete any index.lock and shallow.lock left by a previously canceled run or crashed git process
|
|
const lockPaths = [
|
|
path.join(repositoryPath, '.git', 'index.lock'),
|
|
path.join(repositoryPath, '.git', 'shallow.lock')
|
|
]
|
|
for (const lockPath of lockPaths) {
|
|
try {
|
|
await io.rmRF(lockPath)
|
|
} catch (error) {
|
|
core.debug(
|
|
`Unable to delete '${lockPath}'. ${(error as any)?.message ?? error}`
|
|
)
|
|
}
|
|
}
|
|
|
|
try {
|
|
core.startGroup('Removing previously created refs, to avoid conflicts')
|
|
// Checkout detached HEAD
|
|
if (!(await git.isDetached())) {
|
|
await git.checkoutDetach()
|
|
}
|
|
|
|
// Remove all refs/heads/*
|
|
let branches = await git.branchList(false)
|
|
for (const branch of branches) {
|
|
await git.branchDelete(false, branch)
|
|
}
|
|
|
|
// Remove any conflicting refs/remotes/origin/*
|
|
// Example 1: Consider ref is refs/heads/foo and previously fetched refs/remotes/origin/foo/bar
|
|
// Example 2: Consider ref is refs/heads/foo/bar and previously fetched refs/remotes/origin/foo
|
|
if (ref) {
|
|
ref = ref.startsWith('refs/') ? ref : `refs/heads/${ref}`
|
|
if (ref.startsWith('refs/heads/')) {
|
|
const upperName1 = ref.toUpperCase().substr('REFS/HEADS/'.length)
|
|
const upperName1Slash = `${upperName1}/`
|
|
branches = await git.branchList(true)
|
|
for (const branch of branches) {
|
|
const upperName2 = branch.substr('origin/'.length).toUpperCase()
|
|
const upperName2Slash = `${upperName2}/`
|
|
if (
|
|
upperName1.startsWith(upperName2Slash) ||
|
|
upperName2.startsWith(upperName1Slash)
|
|
) {
|
|
await git.branchDelete(true, branch)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
core.endGroup()
|
|
|
|
// Check for submodules and delete any existing files if submodules are present
|
|
if (!(await git.submoduleStatus())) {
|
|
remove = true
|
|
core.info('Bad Submodules found, removing existing files')
|
|
}
|
|
|
|
// Clean
|
|
if (clean) {
|
|
core.startGroup('Cleaning the repository')
|
|
if (!(await 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 further investigation, manually run 'git clean -ffdx' on the directory '${repositoryPath}'.`
|
|
)
|
|
remove = true
|
|
} else if (!(await git.tryReset())) {
|
|
remove = true
|
|
}
|
|
core.endGroup()
|
|
|
|
if (remove) {
|
|
core.warning(
|
|
`Unable to clean or reset the repository. The repository will be recreated instead.`
|
|
)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
core.warning(
|
|
`Unable to prepare the existing repository. The repository will be recreated instead.`
|
|
)
|
|
remove = true
|
|
}
|
|
}
|
|
|
|
// Check repository conditions
|
|
let isLocalGitRepo = git && fsHelper.directoryExistsSync(path.join(repositoryPath, '.git'));
|
|
let repoUrl = isLocalGitRepo ? await git?.tryGetFetchUrl() : '';
|
|
let isSameRepository = repositoryUrl === repoUrl;
|
|
let differentRepoUrl = !isSameRepository;
|
|
|
|
// Repository URL has changed
|
|
if (differentRepoUrl) {
|
|
if (preserveLocalChanges) {
|
|
core.warning(`Repository URL has changed from '${repoUrl}' to '${repositoryUrl}'. Local changes will be preserved as requested.`);
|
|
}
|
|
remove = true; // Mark for removal, but actual removal will respect preserveLocalChanges
|
|
}
|
|
|
|
if (remove && !preserveLocalChanges) {
|
|
// Delete the contents of the directory. Don't delete the directory itself
|
|
// since it might be the current working directory.
|
|
core.info(`Deleting the contents of '${repositoryPath}'`)
|
|
for (const file of await fs.promises.readdir(repositoryPath)) {
|
|
// Skip .git directory as we need it to determine if a file is tracked
|
|
if (file === '.git') {
|
|
continue
|
|
}
|
|
await io.rmRF(path.join(repositoryPath, file))
|
|
}
|
|
} else if (remove && preserveLocalChanges) {
|
|
core.info(
|
|
`Skipping deletion of directory contents due to preserve-local-changes setting`
|
|
)
|
|
// We still need to make sure we have a git repository to work with
|
|
if (!git) {
|
|
core.info(
|
|
`Initializing git repository to prepare for checkout with preserved changes`
|
|
)
|
|
await fs.promises.mkdir(path.join(repositoryPath, '.git'), {
|
|
recursive: true
|
|
})
|
|
}
|
|
}
|
|
}
|