mirror of
https://github.com/actions/checkout.git
synced 2025-11-28 01:51:57 +02:00
Merge 45abae3e9f into c2d88d3ecc
This commit is contained in:
commit
769a0449fb
10 changed files with 446 additions and 27 deletions
|
|
@ -22,7 +22,7 @@ export interface IGitCommandManager {
|
|||
disableSparseCheckout(): Promise<void>
|
||||
sparseCheckout(sparseCheckout: string[]): Promise<void>
|
||||
sparseCheckoutNonConeMode(sparseCheckout: string[]): Promise<void>
|
||||
checkout(ref: string, startPoint: string): Promise<void>
|
||||
checkout(ref: string, startPoint: string, options?: string[]): Promise<void>
|
||||
checkoutDetach(): Promise<void>
|
||||
config(
|
||||
configKey: string,
|
||||
|
|
@ -221,8 +221,21 @@ class GitCommandManager {
|
|||
)
|
||||
}
|
||||
|
||||
async checkout(ref: string, startPoint: string): Promise<void> {
|
||||
const args = ['checkout', '--progress', '--force']
|
||||
async checkout(
|
||||
ref: string,
|
||||
startPoint: string,
|
||||
options: string[] = []
|
||||
): Promise<void> {
|
||||
const args = ['checkout', '--progress']
|
||||
|
||||
// Add custom options (like --merge) if provided
|
||||
if (options.length > 0) {
|
||||
args.push(...options)
|
||||
} else {
|
||||
// Default behavior - use force
|
||||
args.push('--force')
|
||||
}
|
||||
|
||||
if (startPoint) {
|
||||
args.push('-B', ref, startPoint)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ export async function prepareExistingDirectory(
|
|||
repositoryPath: string,
|
||||
repositoryUrl: string,
|
||||
clean: boolean,
|
||||
ref: string
|
||||
ref: string,
|
||||
preserveLocalChanges: boolean = false
|
||||
): Promise<void> {
|
||||
assert.ok(repositoryPath, 'Expected repositoryPath to be defined')
|
||||
assert.ok(repositoryUrl, 'Expected repositoryUrl to be defined')
|
||||
|
|
@ -19,6 +20,13 @@ export async function prepareExistingDirectory(
|
|||
// 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
|
||||
|
|
@ -114,12 +122,43 @@ export async function prepareExistingDirectory(
|
|||
}
|
||||
}
|
||||
|
||||
if (remove) {
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,8 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
|||
settings.repositoryPath,
|
||||
repositoryUrl,
|
||||
settings.clean,
|
||||
settings.ref
|
||||
settings.ref,
|
||||
settings.preserveLocalChanges
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +230,115 @@ export async function getSource(settings: IGitSourceSettings): Promise<void> {
|
|||
|
||||
// Checkout
|
||||
core.startGroup('Checking out the ref')
|
||||
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
|
||||
if (settings.preserveLocalChanges) {
|
||||
core.info('Attempting to preserve local changes during checkout')
|
||||
|
||||
// List and store local files before checkout
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const localFiles = new Map()
|
||||
|
||||
try {
|
||||
// Get all files in the workspace that aren't in the .git directory
|
||||
const workspacePath = process.cwd()
|
||||
core.info(`Current workspace path: ${workspacePath}`)
|
||||
|
||||
// List all files in the current directory using fs
|
||||
const listFilesRecursively = (dir: string): string[] => {
|
||||
let results: string[] = []
|
||||
const list = fs.readdirSync(dir)
|
||||
list.forEach((file: string) => {
|
||||
const fullPath = path.join(dir, file)
|
||||
const relativePath = path.relative(workspacePath, fullPath)
|
||||
// Skip .git directory
|
||||
if (relativePath.startsWith('.git')) return
|
||||
|
||||
const stat = fs.statSync(fullPath)
|
||||
if (stat && stat.isDirectory()) {
|
||||
// Recursively explore subdirectories
|
||||
results = results.concat(listFilesRecursively(fullPath))
|
||||
} else {
|
||||
// Store file content in memory
|
||||
try {
|
||||
const content = fs.readFileSync(fullPath)
|
||||
localFiles.set(relativePath, content)
|
||||
results.push(relativePath)
|
||||
} catch (readErr) {
|
||||
core.warning(`Failed to read file ${relativePath}: ${readErr}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
return results
|
||||
}
|
||||
|
||||
const localFilesList = listFilesRecursively(workspacePath)
|
||||
core.info(`Found ${localFilesList.length} local files to preserve:`)
|
||||
localFilesList.forEach(file => core.info(` - ${file}`))
|
||||
} catch (error) {
|
||||
core.warning(`Failed to list local files: ${error}`)
|
||||
}
|
||||
|
||||
// Perform normal checkout
|
||||
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
|
||||
|
||||
// Restore local files that were not tracked by git
|
||||
core.info('Restoring local files after checkout')
|
||||
try {
|
||||
let restoredCount = 0
|
||||
const execOptions = {
|
||||
cwd: process.cwd(),
|
||||
silent: true,
|
||||
ignoreReturnCode: true
|
||||
}
|
||||
|
||||
for (const [filePath, content] of localFiles.entries()) {
|
||||
// Check if file exists in git using a child process instead of git.execGit
|
||||
const {exec} = require('@actions/exec')
|
||||
let exitCode = 0
|
||||
const output = {
|
||||
stdout: '',
|
||||
stderr: ''
|
||||
}
|
||||
|
||||
// Capture output
|
||||
const options = {
|
||||
...execOptions,
|
||||
listeners: {
|
||||
stdout: (data: Buffer) => {
|
||||
output.stdout += data.toString()
|
||||
},
|
||||
stderr: (data: Buffer) => {
|
||||
output.stderr += data.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exitCode = await exec(
|
||||
'git',
|
||||
['ls-files', '--error-unmatch', filePath],
|
||||
options
|
||||
)
|
||||
|
||||
if (exitCode !== 0) {
|
||||
// File is not tracked by git, safe to restore
|
||||
const fullPath = path.join(process.cwd(), filePath)
|
||||
// Ensure directory exists
|
||||
fs.mkdirSync(path.dirname(fullPath), {recursive: true})
|
||||
fs.writeFileSync(fullPath, content)
|
||||
core.info(`Restored local file: ${filePath}`)
|
||||
restoredCount++
|
||||
} else {
|
||||
core.info(`Skipping ${filePath} as it's tracked by git`)
|
||||
}
|
||||
}
|
||||
core.info(`Successfully restored ${restoredCount} local files`)
|
||||
} catch (error) {
|
||||
core.warning(`Failed to restore local files: ${error}`)
|
||||
}
|
||||
} else {
|
||||
// Use the default behavior with --force
|
||||
await git.checkout(checkoutInfo.ref, checkoutInfo.startPoint)
|
||||
}
|
||||
core.endGroup()
|
||||
|
||||
// Submodules
|
||||
|
|
|
|||
|
|
@ -25,10 +25,15 @@ export interface IGitSourceSettings {
|
|||
commit: string
|
||||
|
||||
/**
|
||||
* Indicates whether to clean the repository
|
||||
* Whether to execute git clean and git reset before fetching
|
||||
*/
|
||||
clean: boolean
|
||||
|
||||
/**
|
||||
* Whether to preserve local changes during checkout
|
||||
*/
|
||||
preserveLocalChanges: boolean
|
||||
|
||||
/**
|
||||
* The filter determining which objects to include
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -82,6 +82,12 @@ export async function getInputs(): Promise<IGitSourceSettings> {
|
|||
result.clean = (core.getInput('clean') || 'true').toUpperCase() === 'TRUE'
|
||||
core.debug(`clean = ${result.clean}`)
|
||||
|
||||
// Preserve local changes
|
||||
result.preserveLocalChanges =
|
||||
(core.getInput('preserve-local-changes') || 'false').toUpperCase() ===
|
||||
'TRUE'
|
||||
core.debug(`preserveLocalChanges = ${result.preserveLocalChanges}`)
|
||||
|
||||
// Filter
|
||||
const filter = core.getInput('filter')
|
||||
if (filter) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue