in scan/src/utils.ts [214:537]
await git(['checkout', currentBranch])
await git(['cherry-pick', commitToCherryPick])
}
await git(['push', 'origin', currentBranch])
core.info(`Pushed quick-fixes to branch ${currentBranch}`)
} else if (mode === PULL_REQUEST) {
const newBranch = `qodana/quick-fixes-${currentCommit.slice(0, 7)}`
await git(['checkout', '-b', newBranch])
await git(['push', 'origin', newBranch])
await createPr(
commitMessage,
`${c.repo.owner}/${c.repo.repo}`,
currentBranch,
newBranch
)
core.info(
`Pushed quick-fixes to branch ${newBranch} and created pull request`
)
}
} catch (error) {
core.warning(`Failed to push quick fixes – ${(error as Error).message}`)
}
}
export async function prepareAgent(
args: string[],
useNightly = false
): Promise<void> {
const arch = getProcessArchName()
const platform = getProcessPlatformName()
const temp = await tc.downloadTool(getQodanaUrl(arch, platform, useNightly))
if (!useNightly) {
const expectedChecksum = getQodanaSha256(arch, platform)
const actualChecksum = sha256sum(temp)
if (expectedChecksum !== actualChecksum) {
core.setFailed(
getQodanaSha256MismatchMessage(expectedChecksum, actualChecksum)
)
}
}
let extractRoot
if (process.platform === 'win32') {
extractRoot = await tc.extractZip(temp)
} else {
extractRoot = await tc.extractTar(temp)
}
core.addPath(
await tc.cacheDir(extractRoot, EXECUTABLE, useNightly ? 'nightly' : VERSION)
)
if (!isNativeMode(args)) {
const exitCode = await qodana(getInputs(), getQodanaPullArgs(args))
if (exitCode !== 0) {
core.setFailed(`qodana pull failed with exit code ${exitCode}`)
return
}
}
}
/**
* Uploads the Qodana report files from temp directory to GitHub job artifact.
* @param resultsDir The path to upload report from.
* @param artifactName Artifact upload name.
* @param execute whether to execute promise or not.
*/
export async function uploadArtifacts(
resultsDir: string,
artifactName: string,
execute: boolean
): Promise<void> {
if (!execute) {
return
}
try {
const workingDir = path.dirname(resultsDir)
const archivePath = path.join(workingDir, `${artifactName}.zip`)
await compressFolder(resultsDir, archivePath)
await artifact.uploadArtifact(artifactName, [archivePath], workingDir)
} catch (error) {
core.warning(`Failed to upload report – ${(error as Error).message}`)
}
}
/**
* Uploads the cache to GitHub Actions cache from the given path.
* @param cacheDir The path to upload the cache from.
* @param primaryKey Addition to the generated cache hash
* @param reservedCacheKey The cache key to check if the cache already exists.
* @param execute whether to execute promise or not.
*/
export async function uploadCaches(
cacheDir: string,
primaryKey: string,
reservedCacheKey: string,
execute: boolean
): Promise<void> {
if (!execute) {
return
}
if (primaryKey === reservedCacheKey) {
core.info(
`Cache with key ${primaryKey} already exists, skipping cache uploading...`
)
return
}
try {
await cache.saveCache([cacheDir], primaryKey)
} catch (error) {
const errorMessage = (error as Error).message
if (errorMessage.includes('Cache already exists.')) {
core.info(
`Cache with key ${primaryKey} already exists, skipping cache uploading...`
)
} else {
core.warning(`Failed to upload caches – ${errorMessage}`)
}
}
}
/**
* Restores the cache from GitHub Actions cache to the given path.
* @param cacheDir The path to restore the cache to.
* @param primaryKey The primary cache key.
* @param additionalCacheKey The additional cache key.
* @param execute whether to execute promise or not.
*/
export async function restoreCaches(
cacheDir: string,
primaryKey: string,
additionalCacheKey: string,
execute: boolean
): Promise<string> {
if (!execute) {
return ''
}
const restoreKeys = [additionalCacheKey].filter(k => k)
try {
const cacheKey = await cache.restoreCache(
[cacheDir],
primaryKey,
restoreKeys
)
if (!cacheKey) {
core.info(
`No cache found for input keys: ${[primaryKey, ...restoreKeys].join(', ')}.
With cache the pipeline would be faster.`
)
return ''
}
return cacheKey
} catch (error) {
core.warning(
`Failed to restore cache with key ${primaryKey} – ${(error as Error).message}`
)
}
return ''
}
/**
* Check if need to upload the cache.
*/
export function isNeedToUploadCache(
useCaches: boolean,
cacheDefaultBranchOnly: boolean
): boolean {
if (!useCaches && cacheDefaultBranchOnly) {
core.warning(ENABLE_USE_CACHE_OPTION_WARNING)
}
if (useCaches && cacheDefaultBranchOnly) {
const currentBranch = github.context.ref
const defaultBranch = github.context.payload.repository
?.default_branch as string
core.debug(
`Current branch: ${currentBranch} | Default branch: ${defaultBranch}`
)
return currentBranch === `refs/heads/${defaultBranch}`
}
return useCaches
}
/**
* Returns the URL to the current workflow run.
*/
export function getWorkflowRunUrl(): string {
if (!process.env['GITHUB_REPOSITORY']) {
return ''
}
const runId = github.context.runId
const repo = github.context.repo
const serverUrl = process.env['GITHUB_SERVER_URL'] || 'https://github.com'
return `${serverUrl}/${repo.owner}/${repo.repo}/actions/runs/${runId}`
}
/**
* Post a new comment to the pull request.
* @param toolName The name of the tool to mention in comment.
* @param content The comment to post.
* @param sourceDir The analyzed directory inside project
* @param postComment Whether to post a comment or not.
*/
export async function postResultsToPRComments(
toolName: string,
content: string,
sourceDir: string,
postComment: boolean
): Promise<void> {
const pr = github.context.payload.pull_request as
| PullRequestPayload
| undefined
if (!postComment || !pr) {
return
}
// source dir needed in case of monorepo with projects analyzed by the same tool
const comment_tag_pattern = getCommentTag(toolName, sourceDir)
const body = `${content}\n${comment_tag_pattern}`
const client = github.getOctokit(getInputs().githubToken)
const comment_id = await findCommentByTag(client, comment_tag_pattern)
if (comment_id !== -1) {
await updateComment(client, comment_id, body)
} else {
await createComment(client, body)
}
}
/**
* Asynchronously finds a comment on the GitHub issue and returns its ID based on the provided tag. If the
* comment is not found, returns -1. Utilizes GitHub's Octokit REST API client.
*
* @param client The Octokit REST API client to be used for searching for the comment.
* @param tag The string to be searched for in the comments' body.
* @returns A Promise resolving to the comment's ID if found, or -1 if not found or an error occurs.
*/
export async function findCommentByTag(
client: InstanceType<typeof GitHub>,
tag: string
): Promise<number> {
try {
const {data: comments} = await client.rest.issues.listComments({
...github.context.repo,
issue_number: github.context.issue.number
})
const comment = comments.find(c => c?.body?.includes(tag))
return comment ? comment.id : -1
} catch (error) {
core.debug(`Failed to find comment by tag – ${(error as Error).message}`)
return -1
}
}
/**
* Asynchronously creates a comment on the current issue using the provided body text.
* @param client The Octokit REST API client to be used for creating the comment.
* @param body The text content of the comment to be created.
* @returns A Promise that resolves when the comment is successfully created.
*/
export async function createComment(
client: InstanceType<typeof GitHub>,
body: string
): Promise<void> {
try {
await client.rest.issues.createComment({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: github.context.issue.number,
body
})
} catch (error) {
core.debug(`Failed to post comment – ${(error as Error).message}`)
}
}
/**
* Asynchronously updates a GitHub comment with the provided `comment_id` and new content/`body`.
* Handles any occurring errors
* internally by debugging them.
*
* @param client The Octokit REST API client to be used for updating the comment.
* @param comment_id The ID of the GitHub comment to be updated.
* @param body The new content of the comment.
* @returns A Promise that resolves to void after attempted comment update.
*/
export async function updateComment(
client: InstanceType<typeof GitHub>,
comment_id: number,
body: string
): Promise<void> {
try {
await client.rest.issues.updateComment({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
comment_id,
body
})
} catch (error) {
core.debug(`Failed to update comment – ${(error as Error).message}`)
}
}
/**
* Updates the reaction of a pull request review comment to the given 'newReaction'.
* Removes the previous reaction if 'oldReaction' is non-empty.
*
* @param newReaction The new reaction to be added.
* @param oldReaction The old reaction to be removed (if non-empty).
* @returns A Promise resolving to void.
*/
export async function putReaction(
newReaction: Reaction,
oldReaction: string
): Promise<void> {
const pr = github.context.payload.pull_request as
| PullRequestPayload
| undefined
if (!pr) {
return
}
const client = github.getOctokit(getInputs().githubToken)
const issue_number = pr.number
if (oldReaction !== '') {
try {
const {data: reactions} = await client.rest.reactions.listForIssue({