diff --git a/__test__/create-or-update-branch.int.test.ts b/__test__/create-or-update-branch.int.test.ts index a8fa489..3e8414b 100644 --- a/__test__/create-or-update-branch.int.test.ts +++ b/__test__/create-or-update-branch.int.test.ts @@ -1,8 +1,7 @@ import { createOrUpdateBranch, tryFetch, - getWorkingBaseAndType, - buildBranchFileChanges + getWorkingBaseAndType } from '../lib/create-or-update-branch' import * as fs from 'fs' import {GitCommandManager} from '../lib/git-command-manager' @@ -230,80 +229,6 @@ describe('create-or-update-branch tests', () => { expect(workingBaseType).toEqual('commit') }) - it('tests buildBranchFileChanges with no diff', async () => { - await git.checkout(BRANCH, BASE) - const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH) - expect(branchFileChanges.additions.length).toEqual(0) - expect(branchFileChanges.deletions.length).toEqual(0) - }) - - it('tests buildBranchFileChanges with addition and modification', async () => { - await git.checkout(BRANCH, BASE) - const changes = await createChanges() - await git.exec(['add', '-A']) - await git.commit(['-m', 'Test changes']) - - const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH) - - expect(branchFileChanges.additions).toEqual([ - { - path: TRACKED_FILE, - contents: Buffer.from(changes.tracked, 'binary').toString('base64') - }, - { - path: UNTRACKED_FILE, - contents: Buffer.from(changes.untracked, 'binary').toString('base64') - } - ]) - expect(branchFileChanges.deletions.length).toEqual(0) - }) - - it('tests buildBranchFileChanges with addition and deletion', async () => { - await git.checkout(BRANCH, BASE) - const changes = await createChanges() - const TRACKED_FILE_NEW_PATH = 'c/tracked-file.txt' - const filepath = path.join(REPO_PATH, TRACKED_FILE_NEW_PATH) - await fs.promises.mkdir(path.dirname(filepath), {recursive: true}) - await fs.promises.rename(path.join(REPO_PATH, TRACKED_FILE), filepath) - await git.exec(['add', '-A']) - await git.commit(['-m', 'Test changes']) - - const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH) - - expect(branchFileChanges.additions).toEqual([ - { - path: UNTRACKED_FILE, - contents: Buffer.from(changes.untracked, 'binary').toString('base64') - }, - { - path: TRACKED_FILE_NEW_PATH, - contents: Buffer.from(changes.tracked, 'binary').toString('base64') - } - ]) - expect(branchFileChanges.deletions).toEqual([{path: TRACKED_FILE}]) - }) - - it('tests buildBranchFileChanges with binary files', async () => { - await git.checkout(BRANCH, BASE) - const filename = 'c/untracked-binary-file' - const filepath = path.join(REPO_PATH, filename) - const binaryData = Buffer.from([0x00, 0xff, 0x10, 0x20]) - await fs.promises.mkdir(path.dirname(filepath), {recursive: true}) - await fs.promises.writeFile(filepath, binaryData) - await git.exec(['add', '-A']) - await git.commit(['-m', 'Test changes']) - - const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH) - - expect(branchFileChanges.additions).toEqual([ - { - path: filename, - contents: binaryData.toString('base64') - } - ]) - expect(branchFileChanges.deletions.length).toEqual(0) - }) - it('tests no changes resulting in no new branch being created', async () => { const commitMessage = uuidv4() const result = await createOrUpdateBranch( diff --git a/dist/index.js b/dist/index.js index 4a3d5c4..5379444 100644 --- a/dist/index.js +++ b/dist/index.js @@ -42,11 +42,9 @@ Object.defineProperty(exports, "__esModule", ({ value: true })); exports.WorkingBaseType = void 0; exports.getWorkingBaseAndType = getWorkingBaseAndType; exports.tryFetch = tryFetch; -exports.buildBranchFileChanges = buildBranchFileChanges; exports.createOrUpdateBranch = createOrUpdateBranch; const core = __importStar(__nccwpck_require__(2186)); const uuid_1 = __nccwpck_require__(5840); -const utils = __importStar(__nccwpck_require__(918)); const CHERRYPICK_EMPTY = 'The previous cherry-pick is now empty, possibly due to conflict resolution.'; const NOTHING_TO_COMMIT = 'nothing to commit, working tree clean'; const FETCH_DEPTH_MARGIN = 10; @@ -83,35 +81,6 @@ function tryFetch(git, remote, branch, depth) { } }); } -function buildBranchFileChanges(git, base, branch) { - return __awaiter(this, void 0, void 0, function* () { - const branchFileChanges = { - additions: [], - deletions: [] - }; - const changedFiles = yield git.getChangedFiles([ - '--diff-filter=AM', - `${base}..${branch}` - ]); - const deletedFiles = yield git.getChangedFiles([ - '--diff-filter=D', - `${base}..${branch}` - ]); - const repoPath = git.getWorkingDirectory(); - for (const file of changedFiles) { - branchFileChanges.additions.push({ - path: file, - contents: utils.readFileBase64([repoPath, file]) - }); - } - for (const file of deletedFiles) { - branchFileChanges.deletions.push({ - path: file - }); - } - return branchFileChanges; - }); -} // Return the number of commits that branch2 is ahead of branch1 function commitsAhead(git, branch1, branch2) { return __awaiter(this, void 0, void 0, function* () { @@ -290,8 +259,6 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName // Check if the pull request branch is ahead of the base result.hasDiffWithBase = yield isAhead(git, base, branch); } - // Build the branch file changes - result.branchFileChanges = yield buildBranchFileChanges(git, base, branch); // Get the pull request branch SHA result.headSha = yield git.revParse('HEAD'); // Delete the temporary branch @@ -346,9 +313,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.createPullRequest = createPullRequest; const core = __importStar(__nccwpck_require__(2186)); +const fs = __importStar(__nccwpck_require__(7147)); +const graphql_1 = __nccwpck_require__(3414); const create_or_update_branch_1 = __nccwpck_require__(8363); const github_helper_1 = __nccwpck_require__(446); const git_command_manager_1 = __nccwpck_require__(738); @@ -356,6 +336,7 @@ const git_config_helper_1 = __nccwpck_require__(8384); const utils = __importStar(__nccwpck_require__(918)); function createPullRequest(inputs) { return __awaiter(this, void 0, void 0, function* () { + var _a; let gitConfigHelper, git; try { core.startGroup('Prepare git configuration'); @@ -469,7 +450,152 @@ function createPullRequest(inputs) { // The branch was created or updated core.startGroup(`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`); if (inputs.signCommit) { - yield githubHelper.pushSignedCommit(branchRepository, inputs.branch, inputs.base, inputs.commitMessage, result.branchFileChanges); + core.info(`Use API to push a signed commit`); + const graphqlWithAuth = graphql_1.graphql.defaults({ + headers: { + authorization: 'token ' + inputs.token + } + }); + let repoOwner = process.env.GITHUB_REPOSITORY.split('/')[0]; + if (inputs.pushToFork) { + const forkName = yield githubHelper.getRepositoryParent(baseRemote.repository); + if (!forkName) { + repoOwner = forkName; + } + } + const repoName = process.env.GITHUB_REPOSITORY.split('/')[1]; + core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`); + const refQuery = ` + query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) { + repository(owner: $repoOwner, name: $repoName){ + id + ref(qualifiedName: $branchName){ + id + name + prefix + target{ + id + oid + commitUrl + commitResourcePath + abbreviatedOid + } + } + }, + } + `; + let branchRef = yield graphqlWithAuth(refQuery, { + repoOwner: repoOwner, + repoName: repoName, + branchName: inputs.branch + }); + core.debug(`Fetched information for branch '${inputs.branch}' - '${JSON.stringify(branchRef)}'`); + // if the branch does not exist, then first we need to create the branch from base + if (branchRef.repository.ref == null) { + core.debug(`Branch does not exist - '${inputs.branch}'`); + branchRef = yield graphqlWithAuth(refQuery, { + repoOwner: repoOwner, + repoName: repoName, + branchName: inputs.base + }); + core.debug(`Fetched information for base branch '${inputs.base}' - '${JSON.stringify(branchRef)}'`); + core.info(`Creating new branch '${inputs.branch}' from '${inputs.base}', with ref '${JSON.stringify(branchRef.repository.ref.target.oid)}'`); + if (branchRef.repository.ref != null) { + core.debug(`Send request for creating new branch`); + const newBranchMutation = ` + mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) { + createRef(input: { + name: $branchName, + oid: $oid, + repositoryId: $repoId + }) { + ref { + id + name + prefix + } + } + } + `; + const newBranch = yield graphqlWithAuth(newBranchMutation, { + repoId: branchRef.repository.id, + oid: branchRef.repository.ref.target.oid, + branchName: 'refs/heads/' + inputs.branch + }); + core.debug(`Created new branch '${inputs.branch}': '${JSON.stringify(newBranch.createRef.ref)}'`); + } + } + core.info(`Hash ref of branch '${inputs.branch}' is '${JSON.stringify(branchRef.repository.ref.target.oid)}'`); + // switch to input-branch for reading updated file contents + yield git.checkout(inputs.branch); + const changedFiles = yield git.getChangedFiles(branchRef.repository.ref.target.oid, ['--diff-filter=M']); + const deletedFiles = yield git.getChangedFiles(branchRef.repository.ref.target.oid, ['--diff-filter=D']); + const fileChanges = { additions: [], deletions: [] }; + core.debug(`Changed files: '${JSON.stringify(changedFiles)}'`); + core.debug(`Deleted files: '${JSON.stringify(deletedFiles)}'`); + for (const file of changedFiles) { + core.debug(`Reading contents of file: '${file}'`); + fileChanges.additions.push({ + path: file, + contents: fs.readFileSync(file).toString('base64') + }); + } + for (const file of deletedFiles) { + core.debug(`Marking file as deleted: '${file}'`); + fileChanges.deletions.push({ + path: file + }); + } + const pushCommitMutation = ` + mutation PushCommit( + $repoNameWithOwner: String!, + $branchName: String!, + $headOid: GitObjectID!, + $commitMessage: String!, + $fileChanges: FileChanges + ) { + createCommitOnBranch(input: { + branch: { + repositoryNameWithOwner: $repoNameWithOwner, + branchName: $branchName, + } + fileChanges: $fileChanges + message: { + headline: $commitMessage + } + expectedHeadOid: $headOid + }){ + clientMutationId + ref{ + id + name + prefix + } + commit{ + id + abbreviatedOid + oid + } + } + } + `; + const pushCommitVars = { + branchName: inputs.branch, + repoNameWithOwner: repoOwner + '/' + repoName, + headOid: branchRef.repository.ref.target.oid, + commitMessage: inputs.commitMessage, + fileChanges: fileChanges + }; + const pushCommitVarsWithoutContents = Object.assign(Object.assign({}, pushCommitVars), { fileChanges: Object.assign(Object.assign({}, pushCommitVars.fileChanges), { additions: (_a = pushCommitVars.fileChanges.additions) === null || _a === void 0 ? void 0 : _a.map(addition => { + const { contents } = addition, rest = __rest(addition, ["contents"]); + return rest; + }) }) }); + core.debug(`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`); + const commit = yield graphqlWithAuth(pushCommitMutation, pushCommitVars); + core.debug(`Pushed commit - '${JSON.stringify(commit)}'`); + core.info(`Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`); + // switch back to previous branch/state since we are done with reading the changed file contents + yield git.checkout('-'); } else { yield git.push([ @@ -712,12 +838,13 @@ class GitCommandManager { return output.exitCode === 1; }); } - getChangedFiles(options) { + getChangedFiles(ref, options) { return __awaiter(this, void 0, void 0, function* () { const args = ['diff', '--name-only']; if (options) { args.push(...options); } + args.push(ref); const output = yield this.exec(args); return output.stdout.split('\n').filter(filename => filename != ''); }); @@ -1129,17 +1256,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; -var __rest = (this && this.__rest) || function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.GitHubHelper = void 0; const core = __importStar(__nccwpck_require__(2186)); @@ -1256,154 +1372,6 @@ class GitHubHelper { return pull; }); } - pushSignedCommit(branchRepository, branch, base, commitMessage, branchFileChanges) { - return __awaiter(this, void 0, void 0, function* () { - var _a; - core.info(`Use API to push a signed commit`); - const [repoOwner, repoName] = branchRepository.split('/'); - core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`); - const refQuery = ` - query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) { - repository(owner: $repoOwner, name: $repoName){ - id - ref(qualifiedName: $branchName){ - id - name - prefix - target{ - id - oid - commitUrl - commitResourcePath - abbreviatedOid - } - } - }, - } - `; - let branchRef = yield this.octokit.graphql(refQuery, { - repoOwner: repoOwner, - repoName: repoName, - branchName: branch - }); - core.debug(`Fetched information for branch '${branch}' - '${JSON.stringify(branchRef)}'`); - const branchExists = branchRef.repository.ref != null; - // if the branch does not exist, then first we need to create the branch from base - if (!branchExists) { - core.debug(`Branch does not exist - '${branch}'`); - branchRef = yield this.octokit.graphql(refQuery, { - repoOwner: repoOwner, - repoName: repoName, - branchName: base - }); - core.debug(`Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'`); - core.info(`Creating new branch '${branch}' from '${base}', with ref '${JSON.stringify(branchRef.repository.ref.target.oid)}'`); - if (branchRef.repository.ref != null) { - core.debug(`Send request for creating new branch`); - const newBranchMutation = ` - mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) { - createRef(input: { - name: $branchName, - oid: $oid, - repositoryId: $repoId - }) { - ref { - id - name - prefix - } - } - } - `; - const newBranch = yield this.octokit.graphql(newBranchMutation, { - repoId: branchRef.repository.id, - oid: branchRef.repository.ref.target.oid, - branchName: 'refs/heads/' + branch - }); - core.debug(`Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'`); - } - } - core.info(`Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref.target.oid)}'`); - const fileChanges = { - additions: branchFileChanges.additions, - deletions: branchFileChanges.deletions - }; - const pushCommitMutation = ` - mutation PushCommit( - $repoNameWithOwner: String!, - $branchName: String!, - $headOid: GitObjectID!, - $commitMessage: String!, - $fileChanges: FileChanges - ) { - createCommitOnBranch(input: { - branch: { - repositoryNameWithOwner: $repoNameWithOwner, - branchName: $branchName, - } - fileChanges: $fileChanges - message: { - headline: $commitMessage - } - expectedHeadOid: $headOid - }){ - clientMutationId - ref{ - id - name - prefix - } - commit{ - id - abbreviatedOid - oid - } - } - } - `; - const pushCommitVars = { - branchName: branch, - repoNameWithOwner: repoOwner + '/' + repoName, - headOid: branchRef.repository.ref.target.oid, - commitMessage: commitMessage, - fileChanges: fileChanges - }; - const pushCommitVarsWithoutContents = Object.assign(Object.assign({}, pushCommitVars), { fileChanges: Object.assign(Object.assign({}, pushCommitVars.fileChanges), { additions: (_a = pushCommitVars.fileChanges.additions) === null || _a === void 0 ? void 0 : _a.map(addition => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { contents } = addition, rest = __rest(addition, ["contents"]); - return rest; - }) }) }); - core.debug(`Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'`); - const commit = yield this.octokit.graphql(pushCommitMutation, pushCommitVars); - core.debug(`Pushed commit - '${JSON.stringify(commit)}'`); - core.info(`Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'`); - if (branchExists) { - // The branch existed so update the branch ref to point to the new commit - // This is the same behavior as force pushing the branch - core.info(`Updating branch '${branch}' to commit '${commit.createCommitOnBranch.commit.oid}'`); - const updateBranchMutation = ` - mutation UpdateBranch($branchId: ID!, $commitOid: GitObjectID!) { - updateRef(input: { - refId: $branchId, - oid: $commitOid, - force: true - }) { - ref { - id - name - prefix - } - } - } - `; - const updatedBranch = yield this.octokit.graphql(updateBranchMutation, { - branchId: branchRef.repository.ref.id, - commitOid: commit.createCommitOnBranch.commit.oid - }); - core.debug(`Updated branch - '${JSON.stringify(updatedBranch)}'`); - } - }); - } } exports.GitHubHelper = GitHubHelper; @@ -1581,7 +1549,6 @@ exports.randomString = randomString; exports.parseDisplayNameEmail = parseDisplayNameEmail; exports.fileExistsSync = fileExistsSync; exports.readFile = readFile; -exports.readFileBase64 = readFileBase64; exports.getErrorMessage = getErrorMessage; const core = __importStar(__nccwpck_require__(2186)); const fs = __importStar(__nccwpck_require__(7147)); @@ -1671,9 +1638,6 @@ function fileExistsSync(path) { function readFile(path) { return fs.readFileSync(path, 'utf-8'); } -function readFileBase64(pathParts) { - return fs.readFileSync(path.resolve(...pathParts)).toString('base64'); -} /* eslint-disable @typescript-eslint/no-explicit-any */ function hasErrorCode(error) { return typeof (error && error.code) === 'string'; @@ -63383,6 +63347,736 @@ function parseParams (str) { module.exports = parseParams +/***/ }), + +/***/ 3414: +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => { + +"use strict"; +// ESM COMPAT FLAG +__nccwpck_require__.r(__webpack_exports__); + +// EXPORTS +__nccwpck_require__.d(__webpack_exports__, { + "GraphqlResponseError": () => (/* binding */ GraphqlResponseError), + "graphql": () => (/* binding */ graphql2), + "withCustomRequest": () => (/* binding */ withCustomRequest) +}); + +;// CONCATENATED MODULE: ./node_modules/@octokit/graphql/node_modules/universal-user-agent/index.js +function getUserAgent() { + if (typeof navigator === "object" && "userAgent" in navigator) { + return navigator.userAgent; + } + + if (typeof process === "object" && process.version !== undefined) { + return `Node.js/${process.version.substr(1)} (${process.platform}; ${ + process.arch + })`; + } + + return ""; +} + +;// CONCATENATED MODULE: ./node_modules/@octokit/graphql/node_modules/@octokit/endpoint/dist-bundle/index.js +// pkg/dist-src/defaults.js + + +// pkg/dist-src/version.js +var VERSION = "0.0.0-development"; + +// pkg/dist-src/defaults.js +var userAgent = `octokit-endpoint.js/${VERSION} ${getUserAgent()}`; +var DEFAULTS = { + method: "GET", + baseUrl: "https://api.github.com", + headers: { + accept: "application/vnd.github.v3+json", + "user-agent": userAgent + }, + mediaType: { + format: "" + } +}; + +// pkg/dist-src/util/lowercase-keys.js +function lowercaseKeys(object) { + if (!object) { + return {}; + } + return Object.keys(object).reduce((newObj, key) => { + newObj[key.toLowerCase()] = object[key]; + return newObj; + }, {}); +} + +// pkg/dist-src/util/is-plain-object.js +function isPlainObject(value) { + if (typeof value !== "object" || value === null) + return false; + if (Object.prototype.toString.call(value) !== "[object Object]") + return false; + const proto = Object.getPrototypeOf(value); + if (proto === null) + return true; + const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; + return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value); +} + +// pkg/dist-src/util/merge-deep.js +function mergeDeep(defaults, options) { + const result = Object.assign({}, defaults); + Object.keys(options).forEach((key) => { + if (isPlainObject(options[key])) { + if (!(key in defaults)) + Object.assign(result, { [key]: options[key] }); + else + result[key] = mergeDeep(defaults[key], options[key]); + } else { + Object.assign(result, { [key]: options[key] }); + } + }); + return result; +} + +// pkg/dist-src/util/remove-undefined-properties.js +function removeUndefinedProperties(obj) { + for (const key in obj) { + if (obj[key] === void 0) { + delete obj[key]; + } + } + return obj; +} + +// pkg/dist-src/merge.js +function merge(defaults, route, options) { + if (typeof route === "string") { + let [method, url] = route.split(" "); + options = Object.assign(url ? { method, url } : { url: method }, options); + } else { + options = Object.assign({}, route); + } + options.headers = lowercaseKeys(options.headers); + removeUndefinedProperties(options); + removeUndefinedProperties(options.headers); + const mergedOptions = mergeDeep(defaults || {}, options); + if (options.url === "/graphql") { + if (defaults && defaults.mediaType.previews?.length) { + mergedOptions.mediaType.previews = defaults.mediaType.previews.filter( + (preview) => !mergedOptions.mediaType.previews.includes(preview) + ).concat(mergedOptions.mediaType.previews); + } + mergedOptions.mediaType.previews = (mergedOptions.mediaType.previews || []).map((preview) => preview.replace(/-preview/, "")); + } + return mergedOptions; +} + +// pkg/dist-src/util/add-query-parameters.js +function addQueryParameters(url, parameters) { + const separator = /\?/.test(url) ? "&" : "?"; + const names = Object.keys(parameters); + if (names.length === 0) { + return url; + } + return url + separator + names.map((name) => { + if (name === "q") { + return "q=" + parameters.q.split("+").map(encodeURIComponent).join("+"); + } + return `${name}=${encodeURIComponent(parameters[name])}`; + }).join("&"); +} + +// pkg/dist-src/util/extract-url-variable-names.js +var urlVariableRegex = /\{[^}]+\}/g; +function removeNonChars(variableName) { + return variableName.replace(/^\W+|\W+$/g, "").split(/,/); +} +function extractUrlVariableNames(url) { + const matches = url.match(urlVariableRegex); + if (!matches) { + return []; + } + return matches.map(removeNonChars).reduce((a, b) => a.concat(b), []); +} + +// pkg/dist-src/util/omit.js +function omit(object, keysToOmit) { + const result = { __proto__: null }; + for (const key of Object.keys(object)) { + if (keysToOmit.indexOf(key) === -1) { + result[key] = object[key]; + } + } + return result; +} + +// pkg/dist-src/util/url-template.js +function encodeReserved(str) { + return str.split(/(%[0-9A-Fa-f]{2})/g).map(function(part) { + if (!/%[0-9A-Fa-f]/.test(part)) { + part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]"); + } + return part; + }).join(""); +} +function encodeUnreserved(str) { + return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { + return "%" + c.charCodeAt(0).toString(16).toUpperCase(); + }); +} +function encodeValue(operator, value, key) { + value = operator === "+" || operator === "#" ? encodeReserved(value) : encodeUnreserved(value); + if (key) { + return encodeUnreserved(key) + "=" + value; + } else { + return value; + } +} +function isDefined(value) { + return value !== void 0 && value !== null; +} +function isKeyOperator(operator) { + return operator === ";" || operator === "&" || operator === "?"; +} +function getValues(context, operator, key, modifier) { + var value = context[key], result = []; + if (isDefined(value) && value !== "") { + if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + value = value.toString(); + if (modifier && modifier !== "*") { + value = value.substring(0, parseInt(modifier, 10)); + } + result.push( + encodeValue(operator, value, isKeyOperator(operator) ? key : "") + ); + } else { + if (modifier === "*") { + if (Array.isArray(value)) { + value.filter(isDefined).forEach(function(value2) { + result.push( + encodeValue(operator, value2, isKeyOperator(operator) ? key : "") + ); + }); + } else { + Object.keys(value).forEach(function(k) { + if (isDefined(value[k])) { + result.push(encodeValue(operator, value[k], k)); + } + }); + } + } else { + const tmp = []; + if (Array.isArray(value)) { + value.filter(isDefined).forEach(function(value2) { + tmp.push(encodeValue(operator, value2)); + }); + } else { + Object.keys(value).forEach(function(k) { + if (isDefined(value[k])) { + tmp.push(encodeUnreserved(k)); + tmp.push(encodeValue(operator, value[k].toString())); + } + }); + } + if (isKeyOperator(operator)) { + result.push(encodeUnreserved(key) + "=" + tmp.join(",")); + } else if (tmp.length !== 0) { + result.push(tmp.join(",")); + } + } + } + } else { + if (operator === ";") { + if (isDefined(value)) { + result.push(encodeUnreserved(key)); + } + } else if (value === "" && (operator === "&" || operator === "?")) { + result.push(encodeUnreserved(key) + "="); + } else if (value === "") { + result.push(""); + } + } + return result; +} +function parseUrl(template) { + return { + expand: expand.bind(null, template) + }; +} +function expand(template, context) { + var operators = ["+", "#", ".", "/", ";", "?", "&"]; + template = template.replace( + /\{([^\{\}]+)\}|([^\{\}]+)/g, + function(_, expression, literal) { + if (expression) { + let operator = ""; + const values = []; + if (operators.indexOf(expression.charAt(0)) !== -1) { + operator = expression.charAt(0); + expression = expression.substr(1); + } + expression.split(/,/g).forEach(function(variable) { + var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable); + values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3])); + }); + if (operator && operator !== "+") { + var separator = ","; + if (operator === "?") { + separator = "&"; + } else if (operator !== "#") { + separator = operator; + } + return (values.length !== 0 ? operator : "") + values.join(separator); + } else { + return values.join(","); + } + } else { + return encodeReserved(literal); + } + } + ); + if (template === "/") { + return template; + } else { + return template.replace(/\/$/, ""); + } +} + +// pkg/dist-src/parse.js +function parse(options) { + let method = options.method.toUpperCase(); + let url = (options.url || "/").replace(/:([a-z]\w+)/g, "{$1}"); + let headers = Object.assign({}, options.headers); + let body; + let parameters = omit(options, [ + "method", + "baseUrl", + "url", + "headers", + "request", + "mediaType" + ]); + const urlVariableNames = extractUrlVariableNames(url); + url = parseUrl(url).expand(parameters); + if (!/^http/.test(url)) { + url = options.baseUrl + url; + } + const omittedParameters = Object.keys(options).filter((option) => urlVariableNames.includes(option)).concat("baseUrl"); + const remainingParameters = omit(parameters, omittedParameters); + const isBinaryRequest = /application\/octet-stream/i.test(headers.accept); + if (!isBinaryRequest) { + if (options.mediaType.format) { + headers.accept = headers.accept.split(/,/).map( + (format) => format.replace( + /application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/, + `application/vnd$1$2.${options.mediaType.format}` + ) + ).join(","); + } + if (url.endsWith("/graphql")) { + if (options.mediaType.previews?.length) { + const previewsFromAcceptHeader = headers.accept.match(/[\w-]+(?=-preview)/g) || []; + headers.accept = previewsFromAcceptHeader.concat(options.mediaType.previews).map((preview) => { + const format = options.mediaType.format ? `.${options.mediaType.format}` : "+json"; + return `application/vnd.github.${preview}-preview${format}`; + }).join(","); + } + } + } + if (["GET", "HEAD"].includes(method)) { + url = addQueryParameters(url, remainingParameters); + } else { + if ("data" in remainingParameters) { + body = remainingParameters.data; + } else { + if (Object.keys(remainingParameters).length) { + body = remainingParameters; + } + } + } + if (!headers["content-type"] && typeof body !== "undefined") { + headers["content-type"] = "application/json; charset=utf-8"; + } + if (["PATCH", "PUT"].includes(method) && typeof body === "undefined") { + body = ""; + } + return Object.assign( + { method, url, headers }, + typeof body !== "undefined" ? { body } : null, + options.request ? { request: options.request } : null + ); +} + +// pkg/dist-src/endpoint-with-defaults.js +function endpointWithDefaults(defaults, route, options) { + return parse(merge(defaults, route, options)); +} + +// pkg/dist-src/with-defaults.js +function withDefaults(oldDefaults, newDefaults) { + const DEFAULTS2 = merge(oldDefaults, newDefaults); + const endpoint2 = endpointWithDefaults.bind(null, DEFAULTS2); + return Object.assign(endpoint2, { + DEFAULTS: DEFAULTS2, + defaults: withDefaults.bind(null, DEFAULTS2), + merge: merge.bind(null, DEFAULTS2), + parse + }); +} + +// pkg/dist-src/index.js +var endpoint = withDefaults(null, DEFAULTS); + + +;// CONCATENATED MODULE: ./node_modules/@octokit/graphql/node_modules/@octokit/request-error/dist-src/index.js +class RequestError extends Error { + name; + /** + * http status code + */ + status; + /** + * Request options that lead to the error. + */ + request; + /** + * Response object if a response was received + */ + response; + constructor(message, statusCode, options) { + super(message); + this.name = "HttpError"; + this.status = Number.parseInt(statusCode); + if (Number.isNaN(this.status)) { + this.status = 0; + } + if ("response" in options) { + this.response = options.response; + } + const requestCopy = Object.assign({}, options.request); + if (options.request.headers.authorization) { + requestCopy.headers = Object.assign({}, options.request.headers, { + authorization: options.request.headers.authorization.replace( + / .*$/, + " [REDACTED]" + ) + }); + } + requestCopy.url = requestCopy.url.replace(/\bclient_secret=\w+/g, "client_secret=[REDACTED]").replace(/\baccess_token=\w+/g, "access_token=[REDACTED]"); + this.request = requestCopy; + } +} + + +;// CONCATENATED MODULE: ./node_modules/@octokit/graphql/node_modules/@octokit/request/dist-bundle/index.js +// pkg/dist-src/index.js + + +// pkg/dist-src/defaults.js + + +// pkg/dist-src/version.js +var dist_bundle_VERSION = "0.0.0-development"; + +// pkg/dist-src/defaults.js +var defaults_default = { + headers: { + "user-agent": `octokit-request.js/${dist_bundle_VERSION} ${getUserAgent()}` + } +}; + +// pkg/dist-src/is-plain-object.js +function dist_bundle_isPlainObject(value) { + if (typeof value !== "object" || value === null) return false; + if (Object.prototype.toString.call(value) !== "[object Object]") return false; + const proto = Object.getPrototypeOf(value); + if (proto === null) return true; + const Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor; + return typeof Ctor === "function" && Ctor instanceof Ctor && Function.prototype.call(Ctor) === Function.prototype.call(value); +} + +// pkg/dist-src/fetch-wrapper.js + +async function fetchWrapper(requestOptions) { + const fetch = requestOptions.request?.fetch || globalThis.fetch; + if (!fetch) { + throw new Error( + "fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing" + ); + } + const log = requestOptions.request?.log || console; + const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false; + const body = dist_bundle_isPlainObject(requestOptions.body) || Array.isArray(requestOptions.body) ? JSON.stringify(requestOptions.body) : requestOptions.body; + const requestHeaders = Object.fromEntries( + Object.entries(requestOptions.headers).map(([name, value]) => [ + name, + String(value) + ]) + ); + let fetchResponse; + try { + fetchResponse = await fetch(requestOptions.url, { + method: requestOptions.method, + body, + redirect: requestOptions.request?.redirect, + headers: requestHeaders, + signal: requestOptions.request?.signal, + // duplex must be set if request.body is ReadableStream or Async Iterables. + // See https://fetch.spec.whatwg.org/#dom-requestinit-duplex. + ...requestOptions.body && { duplex: "half" } + }); + } catch (error) { + let message = "Unknown Error"; + if (error instanceof Error) { + if (error.name === "AbortError") { + error.status = 500; + throw error; + } + message = error.message; + if (error.name === "TypeError" && "cause" in error) { + if (error.cause instanceof Error) { + message = error.cause.message; + } else if (typeof error.cause === "string") { + message = error.cause; + } + } + } + const requestError = new RequestError(message, 500, { + request: requestOptions + }); + requestError.cause = error; + throw requestError; + } + const status = fetchResponse.status; + const url = fetchResponse.url; + const responseHeaders = {}; + for (const [key, value] of fetchResponse.headers) { + responseHeaders[key] = value; + } + const octokitResponse = { + url, + status, + headers: responseHeaders, + data: "" + }; + if ("deprecation" in responseHeaders) { + const matches = responseHeaders.link && responseHeaders.link.match(/<([^>]+)>; rel="deprecation"/); + const deprecationLink = matches && matches.pop(); + log.warn( + `[@octokit/request] "${requestOptions.method} ${requestOptions.url}" is deprecated. It is scheduled to be removed on ${responseHeaders.sunset}${deprecationLink ? `. See ${deprecationLink}` : ""}` + ); + } + if (status === 204 || status === 205) { + return octokitResponse; + } + if (requestOptions.method === "HEAD") { + if (status < 400) { + return octokitResponse; + } + throw new RequestError(fetchResponse.statusText, status, { + response: octokitResponse, + request: requestOptions + }); + } + if (status === 304) { + octokitResponse.data = await getResponseData(fetchResponse); + throw new RequestError("Not modified", status, { + response: octokitResponse, + request: requestOptions + }); + } + if (status >= 400) { + octokitResponse.data = await getResponseData(fetchResponse); + throw new RequestError(toErrorMessage(octokitResponse.data), status, { + response: octokitResponse, + request: requestOptions + }); + } + octokitResponse.data = parseSuccessResponseBody ? await getResponseData(fetchResponse) : fetchResponse.body; + return octokitResponse; +} +async function getResponseData(response) { + const contentType = response.headers.get("content-type"); + if (/application\/json/.test(contentType)) { + return response.json().catch(() => response.text()).catch(() => ""); + } + if (!contentType || /^text\/|charset=utf-8$/.test(contentType)) { + return response.text(); + } + return response.arrayBuffer(); +} +function toErrorMessage(data) { + if (typeof data === "string") { + return data; + } + if (data instanceof ArrayBuffer) { + return "Unknown error"; + } + if ("message" in data) { + const suffix = "documentation_url" in data ? ` - ${data.documentation_url}` : ""; + return Array.isArray(data.errors) ? `${data.message}: ${data.errors.map((v) => JSON.stringify(v)).join(", ")}${suffix}` : `${data.message}${suffix}`; + } + return `Unknown error: ${JSON.stringify(data)}`; +} + +// pkg/dist-src/with-defaults.js +function dist_bundle_withDefaults(oldEndpoint, newDefaults) { + const endpoint2 = oldEndpoint.defaults(newDefaults); + const newApi = function(route, parameters) { + const endpointOptions = endpoint2.merge(route, parameters); + if (!endpointOptions.request || !endpointOptions.request.hook) { + return fetchWrapper(endpoint2.parse(endpointOptions)); + } + const request2 = (route2, parameters2) => { + return fetchWrapper( + endpoint2.parse(endpoint2.merge(route2, parameters2)) + ); + }; + Object.assign(request2, { + endpoint: endpoint2, + defaults: dist_bundle_withDefaults.bind(null, endpoint2) + }); + return endpointOptions.request.hook(request2, endpointOptions); + }; + return Object.assign(newApi, { + endpoint: endpoint2, + defaults: dist_bundle_withDefaults.bind(null, endpoint2) + }); +} + +// pkg/dist-src/index.js +var request = dist_bundle_withDefaults(endpoint, defaults_default); + + +;// CONCATENATED MODULE: ./node_modules/@octokit/graphql/dist-bundle/index.js +// pkg/dist-src/index.js + + + +// pkg/dist-src/version.js +var graphql_dist_bundle_VERSION = "0.0.0-development"; + +// pkg/dist-src/with-defaults.js + + +// pkg/dist-src/graphql.js + + +// pkg/dist-src/error.js +function _buildMessageForResponseErrors(data) { + return `Request failed due to following response errors: +` + data.errors.map((e) => ` - ${e.message}`).join("\n"); +} +var GraphqlResponseError = class extends Error { + constructor(request2, headers, response) { + super(_buildMessageForResponseErrors(response)); + this.request = request2; + this.headers = headers; + this.response = response; + this.errors = response.errors; + this.data = response.data; + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } + name = "GraphqlResponseError"; + errors; + data; +}; + +// pkg/dist-src/graphql.js +var NON_VARIABLE_OPTIONS = [ + "method", + "baseUrl", + "url", + "headers", + "request", + "query", + "mediaType" +]; +var FORBIDDEN_VARIABLE_OPTIONS = ["query", "method", "url"]; +var GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/; +function graphql(request2, query, options) { + if (options) { + if (typeof query === "string" && "query" in options) { + return Promise.reject( + new Error(`[@octokit/graphql] "query" cannot be used as variable name`) + ); + } + for (const key in options) { + if (!FORBIDDEN_VARIABLE_OPTIONS.includes(key)) + continue; + return Promise.reject( + new Error( + `[@octokit/graphql] "${key}" cannot be used as variable name` + ) + ); + } + } + const parsedOptions = typeof query === "string" ? Object.assign({ query }, options) : query; + const requestOptions = Object.keys( + parsedOptions + ).reduce((result, key) => { + if (NON_VARIABLE_OPTIONS.includes(key)) { + result[key] = parsedOptions[key]; + return result; + } + if (!result.variables) { + result.variables = {}; + } + result.variables[key] = parsedOptions[key]; + return result; + }, {}); + const baseUrl = parsedOptions.baseUrl || request2.endpoint.DEFAULTS.baseUrl; + if (GHES_V3_SUFFIX_REGEX.test(baseUrl)) { + requestOptions.url = baseUrl.replace(GHES_V3_SUFFIX_REGEX, "/api/graphql"); + } + return request2(requestOptions).then((response) => { + if (response.data.errors) { + const headers = {}; + for (const key of Object.keys(response.headers)) { + headers[key] = response.headers[key]; + } + throw new GraphqlResponseError( + requestOptions, + headers, + response.data + ); + } + return response.data.data; + }); +} + +// pkg/dist-src/with-defaults.js +function graphql_dist_bundle_withDefaults(request2, newDefaults) { + const newRequest = request2.defaults(newDefaults); + const newApi = (query, options) => { + return graphql(newRequest, query, options); + }; + return Object.assign(newApi, { + defaults: graphql_dist_bundle_withDefaults.bind(null, newRequest), + endpoint: newRequest.endpoint + }); +} + +// pkg/dist-src/index.js +var graphql2 = graphql_dist_bundle_withDefaults(request, { + headers: { + "user-agent": `octokit-graphql.js/${graphql_dist_bundle_VERSION} ${getUserAgent()}` + }, + method: "POST", + url: "/graphql" +}); +function withCustomRequest(customRequest) { + return graphql_dist_bundle_withDefaults(customRequest, { + method: "POST", + url: "/graphql" + }); +} + + + /***/ }), /***/ 1907: @@ -63426,6 +64120,34 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"] /******/ } /******/ /************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __nccwpck_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __nccwpck_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/make namespace object */ +/******/ (() => { +/******/ // define __esModule on exports +/******/ __nccwpck_require__.r = (exports) => { +/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { +/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); +/******/ } +/******/ Object.defineProperty(exports, '__esModule', { value: true }); +/******/ }; +/******/ })(); +/******/ /******/ /* webpack/runtime/compat */ /******/ /******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; diff --git a/src/create-or-update-branch.ts b/src/create-or-update-branch.ts index 4d354b2..7b584e8 100644 --- a/src/create-or-update-branch.ts +++ b/src/create-or-update-branch.ts @@ -1,7 +1,6 @@ import * as core from '@actions/core' import {GitCommandManager} from './git-command-manager' import {v4 as uuidv4} from 'uuid' -import * as utils from './utils' const CHERRYPICK_EMPTY = 'The previous cherry-pick is now empty, possibly due to conflict resolution.' @@ -48,38 +47,6 @@ export async function tryFetch( } } -export async function buildBranchFileChanges( - git: GitCommandManager, - base: string, - branch: string -): Promise { - const branchFileChanges: BranchFileChanges = { - additions: [], - deletions: [] - } - const changedFiles = await git.getChangedFiles([ - '--diff-filter=AM', - `${base}..${branch}` - ]) - const deletedFiles = await git.getChangedFiles([ - '--diff-filter=D', - `${base}..${branch}` - ]) - const repoPath = git.getWorkingDirectory() - for (const file of changedFiles) { - branchFileChanges.additions!.push({ - path: file, - contents: utils.readFileBase64([repoPath, file]) - }) - } - for (const file of deletedFiles) { - branchFileChanges.deletions!.push({ - path: file - }) - } - return branchFileChanges -} - // Return the number of commits that branch2 is ahead of branch1 async function commitsAhead( git: GitCommandManager, @@ -143,22 +110,11 @@ function splitLines(multilineString: string): string[] { .filter(x => x !== '') } -export interface BranchFileChanges { - additions: { - path: string - contents: string - }[] - deletions: { - path: string - }[] -} - interface CreateOrUpdateBranchResult { action: string base: string hasDiffWithBase: boolean headSha: string - branchFileChanges?: BranchFileChanges } export async function createOrUpdateBranch( @@ -333,9 +289,6 @@ export async function createOrUpdateBranch( result.hasDiffWithBase = await isAhead(git, base, branch) } - // Build the branch file changes - result.branchFileChanges = await buildBranchFileChanges(git, base, branch) - // Get the pull request branch SHA result.headSha = await git.revParse('HEAD') diff --git a/src/create-pull-request.ts b/src/create-pull-request.ts index 0ec2a5e..67eaff4 100644 --- a/src/create-pull-request.ts +++ b/src/create-pull-request.ts @@ -1,4 +1,12 @@ import * as core from '@actions/core' +import * as fs from 'fs' +import {graphql} from '@octokit/graphql' +import type { + Repository, + Ref, + Commit, + FileChanges +} from '@octokit/graphql-schema' import { createOrUpdateBranch, getWorkingBaseAndType, @@ -196,13 +204,207 @@ export async function createPullRequest(inputs: Inputs): Promise { `Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'` ) if (inputs.signCommit) { - await githubHelper.pushSignedCommit( - branchRepository, - inputs.branch, - inputs.base, - inputs.commitMessage, - result.branchFileChanges + core.info(`Use API to push a signed commit`) + const graphqlWithAuth = graphql.defaults({ + headers: { + authorization: 'token ' + inputs.token + } + }) + + let repoOwner = process.env.GITHUB_REPOSITORY!.split('/')[0] + if (inputs.pushToFork) { + const forkName = await githubHelper.getRepositoryParent( + baseRemote.repository + ) + if (!forkName) { + repoOwner = forkName! + } + } + const repoName = process.env.GITHUB_REPOSITORY!.split('/')[1] + + core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`) + const refQuery = ` + query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) { + repository(owner: $repoOwner, name: $repoName){ + id + ref(qualifiedName: $branchName){ + id + name + prefix + target{ + id + oid + commitUrl + commitResourcePath + abbreviatedOid + } + } + }, + } + ` + + let branchRef = await graphqlWithAuth<{repository: Repository}>( + refQuery, + { + repoOwner: repoOwner, + repoName: repoName, + branchName: inputs.branch + } ) + core.debug( + `Fetched information for branch '${inputs.branch}' - '${JSON.stringify(branchRef)}'` + ) + + // if the branch does not exist, then first we need to create the branch from base + if (branchRef.repository.ref == null) { + core.debug(`Branch does not exist - '${inputs.branch}'`) + branchRef = await graphqlWithAuth<{repository: Repository}>( + refQuery, + { + repoOwner: repoOwner, + repoName: repoName, + branchName: inputs.base + } + ) + core.debug( + `Fetched information for base branch '${inputs.base}' - '${JSON.stringify(branchRef)}'` + ) + + core.info( + `Creating new branch '${inputs.branch}' from '${inputs.base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'` + ) + if (branchRef.repository.ref != null) { + core.debug(`Send request for creating new branch`) + const newBranchMutation = ` + mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) { + createRef(input: { + name: $branchName, + oid: $oid, + repositoryId: $repoId + }) { + ref { + id + name + prefix + } + } + } + ` + const newBranch = await graphqlWithAuth<{createRef: {ref: Ref}}>( + newBranchMutation, + { + repoId: branchRef.repository.id, + oid: branchRef.repository.ref.target!.oid, + branchName: 'refs/heads/' + inputs.branch + } + ) + core.debug( + `Created new branch '${inputs.branch}': '${JSON.stringify(newBranch.createRef.ref)}'` + ) + } + } + core.info( + `Hash ref of branch '${inputs.branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'` + ) + + // switch to input-branch for reading updated file contents + await git.checkout(inputs.branch) + + const changedFiles = await git.getChangedFiles( + branchRef.repository.ref!.target!.oid, + ['--diff-filter=M'] + ) + const deletedFiles = await git.getChangedFiles( + branchRef.repository.ref!.target!.oid, + ['--diff-filter=D'] + ) + const fileChanges = {additions: [], deletions: []} + + core.debug(`Changed files: '${JSON.stringify(changedFiles)}'`) + core.debug(`Deleted files: '${JSON.stringify(deletedFiles)}'`) + + for (const file of changedFiles) { + core.debug(`Reading contents of file: '${file}'`) + fileChanges.additions!.push({ + path: file, + contents: fs.readFileSync(file).toString('base64') + }) + } + + for (const file of deletedFiles) { + core.debug(`Marking file as deleted: '${file}'`) + fileChanges.deletions!.push({ + path: file + }) + } + + const pushCommitMutation = ` + mutation PushCommit( + $repoNameWithOwner: String!, + $branchName: String!, + $headOid: GitObjectID!, + $commitMessage: String!, + $fileChanges: FileChanges + ) { + createCommitOnBranch(input: { + branch: { + repositoryNameWithOwner: $repoNameWithOwner, + branchName: $branchName, + } + fileChanges: $fileChanges + message: { + headline: $commitMessage + } + expectedHeadOid: $headOid + }){ + clientMutationId + ref{ + id + name + prefix + } + commit{ + id + abbreviatedOid + oid + } + } + } + ` + const pushCommitVars = { + branchName: inputs.branch, + repoNameWithOwner: repoOwner + '/' + repoName, + headOid: branchRef.repository.ref!.target!.oid, + commitMessage: inputs.commitMessage, + fileChanges: fileChanges + } + + const pushCommitVarsWithoutContents = { + ...pushCommitVars, + fileChanges: { + ...pushCommitVars.fileChanges, + additions: pushCommitVars.fileChanges.additions?.map(addition => { + const {contents, ...rest} = addition + return rest + }) + } + } + + core.debug( + `Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'` + ) + + const commit = await graphqlWithAuth<{ + createCommitOnBranch: {ref: Ref; commit: Commit} + }>(pushCommitMutation, pushCommitVars) + + core.debug(`Pushed commit - '${JSON.stringify(commit)}'`) + core.info( + `Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'` + ) + + // switch back to previous branch/state since we are done with reading the changed file contents + await git.checkout('-') } else { await git.push([ '--force-with-lease', diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 36380a3..65762c1 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -166,11 +166,12 @@ export class GitCommandManager { return output.exitCode === 1 } - async getChangedFiles(options?: string[]): Promise { + async getChangedFiles(ref: string, options?: string[]): Promise { const args = ['diff', '--name-only'] if (options) { args.push(...options) } + args.push(ref) const output = await this.exec(args) return output.stdout.split('\n').filter(filename => filename != '') } diff --git a/src/github-helper.ts b/src/github-helper.ts index c5e637d..d880cfb 100644 --- a/src/github-helper.ts +++ b/src/github-helper.ts @@ -1,13 +1,6 @@ import * as core from '@actions/core' import {Inputs} from './create-pull-request' import {Octokit, OctokitOptions} from './octokit-client' -import type { - Repository as TempRepository, - Ref, - Commit, - FileChanges -} from '@octokit/graphql-schema' -import {BranchFileChanges} from './create-or-update-branch' import * as utils from './utils' const ERROR_PR_REVIEW_TOKEN_SCOPE = @@ -191,204 +184,4 @@ export class GitHubHelper { return pull } - - async pushSignedCommit( - branchRepository: string, - branch: string, - base: string, - commitMessage: string, - branchFileChanges?: BranchFileChanges - ): Promise { - core.info(`Use API to push a signed commit`) - - const [repoOwner, repoName] = branchRepository.split('/') - core.debug(`repoOwner: '${repoOwner}', repoName: '${repoName}'`) - const refQuery = ` - query GetRefId($repoName: String!, $repoOwner: String!, $branchName: String!) { - repository(owner: $repoOwner, name: $repoName){ - id - ref(qualifiedName: $branchName){ - id - name - prefix - target{ - id - oid - commitUrl - commitResourcePath - abbreviatedOid - } - } - }, - } - ` - - let branchRef = await this.octokit.graphql<{repository: TempRepository}>( - refQuery, - { - repoOwner: repoOwner, - repoName: repoName, - branchName: branch - } - ) - core.debug( - `Fetched information for branch '${branch}' - '${JSON.stringify(branchRef)}'` - ) - - const branchExists = branchRef.repository.ref != null - - // if the branch does not exist, then first we need to create the branch from base - if (!branchExists) { - core.debug(`Branch does not exist - '${branch}'`) - branchRef = await this.octokit.graphql<{repository: TempRepository}>( - refQuery, - { - repoOwner: repoOwner, - repoName: repoName, - branchName: base - } - ) - core.debug( - `Fetched information for base branch '${base}' - '${JSON.stringify(branchRef)}'` - ) - - core.info( - `Creating new branch '${branch}' from '${base}', with ref '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'` - ) - if (branchRef.repository.ref != null) { - core.debug(`Send request for creating new branch`) - const newBranchMutation = ` - mutation CreateNewBranch($branchName: String!, $oid: GitObjectID!, $repoId: ID!) { - createRef(input: { - name: $branchName, - oid: $oid, - repositoryId: $repoId - }) { - ref { - id - name - prefix - } - } - } - ` - const newBranch = await this.octokit.graphql<{createRef: {ref: Ref}}>( - newBranchMutation, - { - repoId: branchRef.repository.id, - oid: branchRef.repository.ref.target!.oid, - branchName: 'refs/heads/' + branch - } - ) - core.debug( - `Created new branch '${branch}': '${JSON.stringify(newBranch.createRef.ref)}'` - ) - } - } - core.info( - `Hash ref of branch '${branch}' is '${JSON.stringify(branchRef.repository.ref!.target!.oid)}'` - ) - - const fileChanges = { - additions: branchFileChanges!.additions, - deletions: branchFileChanges!.deletions - } - - const pushCommitMutation = ` - mutation PushCommit( - $repoNameWithOwner: String!, - $branchName: String!, - $headOid: GitObjectID!, - $commitMessage: String!, - $fileChanges: FileChanges - ) { - createCommitOnBranch(input: { - branch: { - repositoryNameWithOwner: $repoNameWithOwner, - branchName: $branchName, - } - fileChanges: $fileChanges - message: { - headline: $commitMessage - } - expectedHeadOid: $headOid - }){ - clientMutationId - ref{ - id - name - prefix - } - commit{ - id - abbreviatedOid - oid - } - } - } - ` - const pushCommitVars = { - branchName: branch, - repoNameWithOwner: repoOwner + '/' + repoName, - headOid: branchRef.repository.ref!.target!.oid, - commitMessage: commitMessage, - fileChanges: fileChanges - } - - const pushCommitVarsWithoutContents = { - ...pushCommitVars, - fileChanges: { - ...pushCommitVars.fileChanges, - additions: pushCommitVars.fileChanges.additions?.map(addition => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {contents, ...rest} = addition - return rest - }) - } - } - - core.debug( - `Push commit with payload: '${JSON.stringify(pushCommitVarsWithoutContents)}'` - ) - - const commit = await this.octokit.graphql<{ - createCommitOnBranch: {ref: Ref; commit: Commit} - }>(pushCommitMutation, pushCommitVars) - - core.debug(`Pushed commit - '${JSON.stringify(commit)}'`) - core.info( - `Pushed commit with hash - '${commit.createCommitOnBranch.commit.oid}' on branch - '${commit.createCommitOnBranch.ref.name}'` - ) - - if (branchExists) { - // The branch existed so update the branch ref to point to the new commit - // This is the same behavior as force pushing the branch - core.info( - `Updating branch '${branch}' to commit '${commit.createCommitOnBranch.commit.oid}'` - ) - const updateBranchMutation = ` - mutation UpdateBranch($branchId: ID!, $commitOid: GitObjectID!) { - updateRef(input: { - refId: $branchId, - oid: $commitOid, - force: true - }) { - ref { - id - name - prefix - } - } - } - ` - const updatedBranch = await this.octokit.graphql<{updateRef: {ref: Ref}}>( - updateBranchMutation, - { - branchId: branchRef.repository.ref!.id, - commitOid: commit.createCommitOnBranch.commit.oid - } - ) - core.debug(`Updated branch - '${JSON.stringify(updatedBranch)}'`) - } - } } diff --git a/src/utils.ts b/src/utils.ts index ebde59e..b501dd4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -126,10 +126,6 @@ export function readFile(path: string): string { return fs.readFileSync(path, 'utf-8') } -export function readFileBase64(pathParts: string[]): string { - return fs.readFileSync(path.resolve(...pathParts)).toString('base64') -} - /* eslint-disable @typescript-eslint/no-explicit-any */ function hasErrorCode(error: any): error is {code: string} { return typeof (error && error.code) === 'string'