feat: signed commits (v7) (#3057)

* Add support for signed commits (#3055)

* formatting

* fix eslint and lint errors

* shift setting the base to before the push

* sign commits by default for testing

* add debug lines

* read to buffer not string and use non-legacy method to base64

* debug payload without contents

* disable linter for debug code

* fix filepath when using path input

* try to fix head repo

* remove commented code

* Try refactor of file changes

* add tests for building file changes

* add build file changes test for binary files

* refactor graphql code into github helper class

* build file changes even when there is no diff

* add function to get commit detail

* fix format

* build branch commits

* use source mode for deleted files

* try rest api route

* fix check for branch existence

* force push

* try fix base tree

* debug commit verification

* debug commit verification

* fix format and cleanup

* add executable mode file to test

* limit blob creation concurrency

* only build commits when feature enabled

* remove unused code

* update readme link

* update docs for commit signing

* fix capital letter

* update docs

* add throttling

* set default back to false

* output head sha and verified status

* log outputs

* fix head sha output

* default the operation output to none

* output retryafter for secondary rate limit

* use separate client for branch and pull operations

* add maintainer-can-modify input

* rename git-token to branch-token

* fix branch token input

* remove deprecated env output

* update docs

* fix doc

* update docs

* build branch commits when there is a diff with the base

* check verification status of head commit when not known

* fix verified output when no commit signing is being used

* draft always-true

* convert to draft on branch updates when there is a diff with base

* update docs with blob size limit

* catch errors during blob creation for debugging

* parse empty commits

* pass base commit to push signed commits

* use parent commit details in create commit

* use parent tree for base_tree

* multipart tree creation

* update docs

* update readme about the permissions of the default token

* fix edge case where changes are partially merged

* add updating documentation

* fix typo

* update major version

---------

Co-authored-by: Ravi <1299606+rustycl0ck@users.noreply.github.com>
This commit is contained in:
Peter Evans 2024-09-03 08:54:12 +01:00 committed by GitHub
parent 0c2a66fe4a
commit 4320041ed3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 32759 additions and 8594 deletions

View file

@ -1,5 +1,5 @@
import * as core from '@actions/core'
import {GitCommandManager} from './git-command-manager'
import {GitCommandManager, Commit} from './git-command-manager'
import {v4 as uuidv4} from 'uuid'
const CHERRYPICK_EMPTY =
@ -47,6 +47,27 @@ export async function tryFetch(
}
}
export async function buildBranchCommits(
git: GitCommandManager,
base: string,
branch: string
): Promise<Commit[]> {
const output = await git.exec(['log', '--format=%H', `${base}..${branch}`])
const shas = output.stdout
.split('\n')
.filter(x => x !== '')
.reverse()
const commits: Commit[] = []
for (const sha of shas) {
const commit = await git.getCommit(sha)
commits.push(commit)
for (const unparsedChange of commit.unparsedChanges) {
core.warning(`Skipping unexpected diff entry: ${unparsedChange}`)
}
}
return commits
}
// Return the number of commits that branch2 is ahead of branch1
async function commitsAhead(
git: GitCommandManager,
@ -103,6 +124,22 @@ async function isEven(
)
}
// Return true if the specified number of commits on branch1 and branch2 have a diff
async function commitsHaveDiff(
git: GitCommandManager,
branch1: string,
branch2: string,
depth: number
): Promise<boolean> {
const diff1 = (
await git.exec(['diff', '--stat', `${branch1}..${branch1}~${depth}`])
).stdout.trim()
const diff2 = (
await git.exec(['diff', '--stat', `${branch2}..${branch2}~${depth}`])
).stdout.trim()
return diff1 !== diff2
}
function splitLines(multilineString: string): string[] {
return multilineString
.split('\n')
@ -114,7 +151,9 @@ interface CreateOrUpdateBranchResult {
action: string
base: string
hasDiffWithBase: boolean
baseCommit: Commit
headSha: string
branchCommits: Commit[]
}
export async function createOrUpdateBranch(
@ -139,14 +178,6 @@ export async function createOrUpdateBranch(
base = base ? base : workingBase
const baseRemote = 'origin'
// Set the default return values
const result: CreateOrUpdateBranchResult = {
action: 'none',
base: base,
hasDiffWithBase: false,
headSha: ''
}
// Save the working base changes to a temporary branch
const tempBranch = uuidv4()
await git.checkout(tempBranch, 'HEAD')
@ -226,6 +257,9 @@ export async function createOrUpdateBranch(
? tempBranchCommitsAhead + FETCH_DEPTH_MARGIN
: FETCH_DEPTH_MARGIN
let action = 'none'
let hasDiffWithBase = false
// Try to fetch the pull request branch
if (!(await tryFetch(git, branchRemoteName, branch, fetchDepth))) {
// The pull request branch does not exist
@ -233,9 +267,9 @@ export async function createOrUpdateBranch(
// Create the pull request branch
await git.checkout(branch, tempBranch)
// Check if the pull request branch is ahead of the base
result.hasDiffWithBase = await isAhead(git, base, branch)
if (result.hasDiffWithBase) {
result.action = 'created'
hasDiffWithBase = await isAhead(git, base, branch)
if (hasDiffWithBase) {
action = 'created'
core.info(`Created branch '${branch}'`)
} else {
core.info(
@ -252,20 +286,26 @@ export async function createOrUpdateBranch(
// Reset the branch if one of the following conditions is true.
// - If the branch differs from the recreated temp branch.
// - If the number of commits ahead of the base branch differs between the branch and
// temp branch. This catches a case where the base branch has been force pushed to
// a new commit.
// - If the recreated temp branch is not ahead of the base. This means there will be
// no pull request diff after the branch is reset. This will reset any undeleted
// branches after merging. In particular, it catches a case where the branch was
// squash merged but not deleted. We need to reset to make sure it doesn't appear
// to have a diff with the base due to different commits for the same changes.
// - If the number of commits ahead of the base branch differs between the branch and
// temp branch. This catches a case where the base branch has been force pushed to
// a new commit.
// - If the diff of the commits ahead of the base branch differs between the branch and
// temp branch. This catches a case where changes have been partially merged to the
// base. The overall diff is the same, but the branch needs to be rebased to show
// the correct diff.
//
// For changes on base this reset is equivalent to a rebase of the pull request branch.
const branchCommitsAhead = await commitsAhead(git, base, branch)
if (
(await git.hasDiff([`${branch}..${tempBranch}`])) ||
branchCommitsAhead != tempBranchCommitsAhead ||
!(tempBranchCommitsAhead > 0) // !isAhead
!(tempBranchCommitsAhead > 0) || // !isAhead
(await commitsHaveDiff(git, branch, tempBranch, tempBranchCommitsAhead))
) {
core.info(`Resetting '${branch}'`)
// Alternatively, git switch -C branch tempBranch
@ -276,21 +316,29 @@ export async function createOrUpdateBranch(
// If the branch was reset or updated it will be ahead
// It may be behind if a reset now results in no diff with the base
if (!(await isEven(git, `${branchRemoteName}/${branch}`, branch))) {
result.action = 'updated'
action = 'updated'
core.info(`Updated branch '${branch}'`)
} else {
result.action = 'not-updated'
action = 'not-updated'
core.info(
`Branch '${branch}' is even with its remote and will not be updated`
)
}
// Check if the pull request branch is ahead of the base
result.hasDiffWithBase = await isAhead(git, base, branch)
hasDiffWithBase = await isAhead(git, base, branch)
}
// Get the pull request branch SHA
result.headSha = await git.revParse('HEAD')
// Get the base and head SHAs
const baseSha = await git.revParse(base)
const baseCommit = await git.getCommit(baseSha)
const headSha = await git.revParse(branch)
let branchCommits: Commit[] = []
if (hasDiffWithBase) {
// Build the branch commits
branchCommits = await buildBranchCommits(git, base, branch)
}
// Delete the temporary branch
await git.exec(['branch', '--delete', '--force', tempBranch])
@ -303,5 +351,12 @@ export async function createOrUpdateBranch(
await git.stashPop()
}
return result
return {
action: action,
base: base,
hasDiffWithBase: hasDiffWithBase,
baseCommit: baseCommit,
headSha: headSha,
branchCommits: branchCommits
}
}