Compare commits
4 Commits
signed-com
...
commit-wit
Author | SHA1 | Date | |
---|---|---|---|
9b00b13f5b | |||
3707121594 | |||
058cde4277 | |||
a0390aed21 |
@ -2,7 +2,8 @@ import {
|
||||
createOrUpdateBranch,
|
||||
tryFetch,
|
||||
getWorkingBaseAndType,
|
||||
buildBranchFileChanges
|
||||
buildBranchFileChanges,
|
||||
buildBranchCommits
|
||||
} from '../lib/create-or-update-branch'
|
||||
import * as fs from 'fs'
|
||||
import {GitCommandManager} from '../lib/git-command-manager'
|
||||
@ -230,6 +231,73 @@ describe('create-or-update-branch tests', () => {
|
||||
expect(workingBaseType).toEqual('commit')
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with no diff', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
expect(branchCommits.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and modification', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', 'Test changes'])
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(2)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with addition and deletion', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
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 branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(1)
|
||||
expect(branchCommits[0].subject).toEqual('Test changes')
|
||||
expect(branchCommits[0].changes.length).toEqual(3)
|
||||
expect(branchCommits[0].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'D'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: 'A'},
|
||||
{mode: '100644', path: TRACKED_FILE_NEW_PATH, status: 'A'}
|
||||
])
|
||||
})
|
||||
|
||||
it('tests buildBranchCommits with multiple commits', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await createChanges()
|
||||
await git.exec(['add', '-A'])
|
||||
await git.commit(['-m', `Test changes ${i}`])
|
||||
}
|
||||
|
||||
const branchCommits = await buildBranchCommits(git, BASE, BRANCH)
|
||||
|
||||
expect(branchCommits.length).toEqual(3)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
expect(branchCommits[i].subject).toEqual(`Test changes ${i}`)
|
||||
expect(branchCommits[i].changes.length).toEqual(2)
|
||||
const untrackedFileStatus = i == 0 ? 'A' : 'M'
|
||||
expect(branchCommits[i].changes).toEqual([
|
||||
{mode: '100644', path: TRACKED_FILE, status: 'M'},
|
||||
{mode: '100644', path: UNTRACKED_FILE, status: untrackedFileStatus}
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
it('tests buildBranchFileChanges with no diff', async () => {
|
||||
await git.checkout(BRANCH, BASE)
|
||||
const branchFileChanges = await buildBranchFileChanges(git, BASE, BRANCH)
|
||||
|
@ -13,7 +13,7 @@ git daemon --verbose --enable=receive-pack --base-path=/git/remote --export-all
|
||||
# Give the daemon time to start
|
||||
sleep 2
|
||||
|
||||
# Create a local clone and make an initial commit
|
||||
# Create a local clone and make initial commits
|
||||
mkdir -p /git/local/repos
|
||||
git clone git://127.0.0.1/repos/test-base.git /git/local/repos/test-base
|
||||
cd /git/local/repos/test-base
|
||||
@ -22,6 +22,10 @@ git config --global user.name "Your Name"
|
||||
echo "#test-base" > README.md
|
||||
git add .
|
||||
git commit -m "initial commit"
|
||||
echo "#test-base :sparkles:" > README.md
|
||||
git add .
|
||||
git commit -m "add sparkles" -m "Change description:
|
||||
- updates README.md to add sparkles to the title"
|
||||
git push -u
|
||||
git log -1 --pretty=oneline
|
||||
git config --global --unset user.email
|
||||
|
26
__test__/git-command-manager.int.test.ts
Normal file
26
__test__/git-command-manager.int.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import {GitCommandManager, Commit} from '../lib/git-command-manager'
|
||||
|
||||
const REPO_PATH = '/git/local/repos/test-base'
|
||||
|
||||
describe('git-command-manager integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
await git.checkout('main')
|
||||
})
|
||||
|
||||
it('tests getCommit', async () => {
|
||||
const parent = await git.getCommit('HEAD^')
|
||||
const commit = await git.getCommit('HEAD')
|
||||
expect(parent.subject).toEqual('initial commit')
|
||||
expect(parent.changes).toEqual([
|
||||
{mode: '100644', status: 'A', path: 'README.md'}
|
||||
])
|
||||
expect(commit.subject).toEqual('add sparkles')
|
||||
expect(commit.parents[0]).toEqual(parent.sha)
|
||||
expect(commit.changes).toEqual([
|
||||
{mode: '100644', status: 'M', path: 'README.md'}
|
||||
])
|
||||
})
|
||||
})
|
@ -7,7 +7,6 @@ const extraheaderConfigKey = 'http.https://127.0.0.1/.extraheader'
|
||||
|
||||
describe('git-config-helper integration tests', () => {
|
||||
let git: GitCommandManager
|
||||
let gitConfigHelper: GitConfigHelper
|
||||
|
||||
beforeAll(async () => {
|
||||
git = await GitCommandManager.create(REPO_PATH)
|
||||
|
156
dist/index.js
vendored
156
dist/index.js
vendored
@ -42,6 +42,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.WorkingBaseType = void 0;
|
||||
exports.getWorkingBaseAndType = getWorkingBaseAndType;
|
||||
exports.tryFetch = tryFetch;
|
||||
exports.buildBranchCommits = buildBranchCommits;
|
||||
exports.buildBranchFileChanges = buildBranchFileChanges;
|
||||
exports.createOrUpdateBranch = createOrUpdateBranch;
|
||||
const core = __importStar(__nccwpck_require__(2186));
|
||||
@ -83,6 +84,21 @@ function tryFetch(git, remote, branch, depth) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function buildBranchCommits(git, base, branch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const output = yield git.exec(['log', '--format=%H', `${base}..${branch}`]);
|
||||
const shas = output.stdout
|
||||
.split('\n')
|
||||
.filter(x => x !== '')
|
||||
.reverse();
|
||||
const commits = [];
|
||||
for (const sha of shas) {
|
||||
const commit = yield git.getCommit(sha);
|
||||
commits.push(commit);
|
||||
}
|
||||
return commits;
|
||||
});
|
||||
}
|
||||
function buildBranchFileChanges(git, base, branch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const branchFileChanges = {
|
||||
@ -169,7 +185,8 @@ function createOrUpdateBranch(git, commitMessage, base, branch, branchRemoteName
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false,
|
||||
headSha: ''
|
||||
headSha: '',
|
||||
branchCommits: []
|
||||
};
|
||||
// Save the working base changes to a temporary branch
|
||||
const tempBranch = (0, uuid_1.v4)();
|
||||
@ -290,6 +307,8 @@ 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 commits
|
||||
result.branchCommits = yield buildBranchCommits(git, base, branch);
|
||||
// Build the branch file changes
|
||||
result.branchFileChanges = yield buildBranchFileChanges(git, base, branch);
|
||||
// Get the pull request branch SHA
|
||||
@ -469,7 +488,21 @@ 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);
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = yield git.stashPush(['--include-untracked']);
|
||||
yield git.checkout(inputs.branch);
|
||||
yield githubHelper.pushSignedCommits(result.branchCommits, repoPath, branchRepository, inputs.branch);
|
||||
// await githubHelper.pushSignedCommit(
|
||||
// branchRepository,
|
||||
// inputs.branch,
|
||||
// inputs.base,
|
||||
// inputs.commitMessage,
|
||||
// result.branchFileChanges
|
||||
// )
|
||||
yield git.checkout('-');
|
||||
if (stashed) {
|
||||
yield git.stashPop();
|
||||
}
|
||||
}
|
||||
else {
|
||||
yield git.push([
|
||||
@ -684,6 +717,42 @@ class GitCommandManager {
|
||||
yield this.exec(args);
|
||||
});
|
||||
}
|
||||
getCommit(ref) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const endOfBody = '###EOB###';
|
||||
const output = yield this.exec([
|
||||
'show',
|
||||
'--raw',
|
||||
'--cc',
|
||||
'--diff-filter=AMD',
|
||||
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
|
||||
ref
|
||||
]);
|
||||
const lines = output.stdout.split('\n');
|
||||
const endOfBodyIndex = lines.lastIndexOf(endOfBody);
|
||||
const detailLines = lines.slice(0, endOfBodyIndex);
|
||||
return {
|
||||
sha: detailLines[0],
|
||||
tree: detailLines[1],
|
||||
parents: detailLines[2].split(' '),
|
||||
subject: detailLines[3],
|
||||
body: detailLines.slice(4, endOfBodyIndex).join('\n'),
|
||||
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
|
||||
const change = line.match(/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/);
|
||||
if (change) {
|
||||
return {
|
||||
mode: change[3] === 'D' ? change[1] : change[2],
|
||||
status: change[3],
|
||||
path: change[4]
|
||||
};
|
||||
}
|
||||
else {
|
||||
throw new Error(`Unexpected line format: ${line}`);
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
getConfigValue(configKey_1) {
|
||||
return __awaiter(this, arguments, void 0, function* (configKey, configValue = '.') {
|
||||
const output = yield this.exec([
|
||||
@ -1256,6 +1325,61 @@ class GitHubHelper {
|
||||
return pull;
|
||||
});
|
||||
}
|
||||
pushSignedCommits(branchCommits, repoPath, branchRepository, branch) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let headSha = '';
|
||||
for (const commit of branchCommits) {
|
||||
headSha = yield this.createCommit(commit, repoPath, branchRepository);
|
||||
}
|
||||
yield this.createOrUpdateRef(branchRepository, branch, headSha);
|
||||
});
|
||||
}
|
||||
createCommit(commit, repoPath, branchRepository) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const repository = this.parseRepository(branchRepository);
|
||||
let treeSha = commit.tree;
|
||||
if (commit.changes.length > 0) {
|
||||
core.debug(`Creating tree objects for local commit ${commit.sha}`);
|
||||
const treeObjects = yield Promise.all(commit.changes.map((_a) => __awaiter(this, [_a], void 0, function* ({ path, mode, status }) {
|
||||
let sha = null;
|
||||
if (status === 'A' || status === 'M') {
|
||||
core.debug(`Creating blob for file '${path}'`);
|
||||
const { data: blob } = yield this.octokit.rest.git.createBlob(Object.assign(Object.assign({}, repository), { content: utils.readFileBase64([repoPath, path]), encoding: 'base64' }));
|
||||
sha = blob.sha;
|
||||
}
|
||||
return {
|
||||
path,
|
||||
mode,
|
||||
sha,
|
||||
type: 'blob'
|
||||
};
|
||||
})));
|
||||
core.debug(`Creating tree for local commit ${commit.sha}`);
|
||||
const { data: tree } = yield this.octokit.rest.git.createTree(Object.assign(Object.assign({}, repository), { base_tree: commit.parents[0], tree: treeObjects }));
|
||||
treeSha = tree.sha;
|
||||
core.debug(`Created tree ${treeSha} for local commit ${commit.sha}`);
|
||||
}
|
||||
const { data: remoteCommit } = yield this.octokit.rest.git.createCommit(Object.assign(Object.assign({}, repository), { parents: commit.parents, tree: treeSha, message: `${commit.subject}\n\n${commit.body}` }));
|
||||
core.debug(`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`);
|
||||
return remoteCommit.sha;
|
||||
});
|
||||
}
|
||||
createOrUpdateRef(branchRepository, branch, newHead) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const repository = this.parseRepository(branchRepository);
|
||||
const branchExists = yield this.octokit.rest.git
|
||||
.getRef(Object.assign(Object.assign({}, repository), { ref: branch }))
|
||||
.then(() => true, () => false);
|
||||
if (branchExists) {
|
||||
core.debug(`Branch ${branch} exists, updating ref`);
|
||||
yield this.octokit.rest.git.updateRef(Object.assign(Object.assign({}, repository), { sha: newHead, ref: `heads/${branch}` }));
|
||||
}
|
||||
else {
|
||||
core.debug(`Branch ${branch} does not exist, creating ref`);
|
||||
yield this.octokit.rest.git.createRef(Object.assign(Object.assign({}, repository), { sha: newHead, ref: `refs/heads/${branch}` }));
|
||||
}
|
||||
});
|
||||
}
|
||||
pushSignedCommit(branchRepository, branch, base, commitMessage, branchFileChanges) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
var _a;
|
||||
@ -1287,9 +1411,8 @@ class GitHubHelper {
|
||||
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) {
|
||||
if (branchRef.repository.ref == null) {
|
||||
core.debug(`Branch does not exist - '${branch}'`);
|
||||
branchRef = yield this.octokit.graphql(refQuery, {
|
||||
repoOwner: repoOwner,
|
||||
@ -1377,31 +1500,6 @@ class GitHubHelper {
|
||||
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)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
24
package-lock.json
generated
24
package-lock.json
generated
@ -17,12 +17,12 @@
|
||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"undici": "^6.19.4",
|
||||
"undici": "^6.19.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.42",
|
||||
"@types/node": "^18.19.43",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
@ -37,7 +37,7 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.3",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
},
|
||||
@ -1639,9 +1639,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.19.42",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz",
|
||||
"integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==",
|
||||
"version": "18.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz",
|
||||
"integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
@ -7553,9 +7553,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-jest": {
|
||||
"version": "29.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.3.tgz",
|
||||
"integrity": "sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ==",
|
||||
"version": "29.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz",
|
||||
"integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"bs-logger": "0.x",
|
||||
@ -7802,9 +7802,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.19.4",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.4.tgz",
|
||||
"integrity": "sha512-i3uaEUwNdkRq2qtTRRJb13moW5HWqviu7Vl7oYRYz++uPtGHJj+x7TGjcEuwS5Mt2P4nA0U9dhIX3DdB6JGY0g==",
|
||||
"version": "6.19.5",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.5.tgz",
|
||||
"integrity": "sha512-LryC15SWzqQsREHIOUybavaIHF5IoL0dJ9aWWxL/PgT1KfqAW5225FZpDUFlt9xiDMS2/S7DOKhFWA7RLksWdg==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
|
@ -37,12 +37,12 @@
|
||||
"@octokit/plugin-paginate-rest": "^5.0.1",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^6.8.1",
|
||||
"proxy-from-env": "^1.1.0",
|
||||
"undici": "^6.19.4",
|
||||
"undici": "^6.19.5",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^18.19.42",
|
||||
"@types/node": "^18.19.43",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
@ -57,7 +57,7 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"prettier": "^3.3.3",
|
||||
"ts-jest": "^29.2.3",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
import * as utils from './utils'
|
||||
|
||||
@ -48,6 +48,24 @@ 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)
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
export async function buildBranchFileChanges(
|
||||
git: GitCommandManager,
|
||||
base: string,
|
||||
@ -159,6 +177,7 @@ interface CreateOrUpdateBranchResult {
|
||||
hasDiffWithBase: boolean
|
||||
headSha: string
|
||||
branchFileChanges?: BranchFileChanges
|
||||
branchCommits: Commit[]
|
||||
}
|
||||
|
||||
export async function createOrUpdateBranch(
|
||||
@ -188,7 +207,8 @@ export async function createOrUpdateBranch(
|
||||
action: 'none',
|
||||
base: base,
|
||||
hasDiffWithBase: false,
|
||||
headSha: ''
|
||||
headSha: '',
|
||||
branchCommits: []
|
||||
}
|
||||
|
||||
// Save the working base changes to a temporary branch
|
||||
@ -333,6 +353,9 @@ export async function createOrUpdateBranch(
|
||||
result.hasDiffWithBase = await isAhead(git, base, branch)
|
||||
}
|
||||
|
||||
// Build the branch commits
|
||||
result.branchCommits = await buildBranchCommits(git, base, branch)
|
||||
|
||||
// Build the branch file changes
|
||||
result.branchFileChanges = await buildBranchFileChanges(git, base, branch)
|
||||
|
||||
|
@ -196,13 +196,26 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
|
||||
`Pushing pull request branch to '${branchRemoteName}/${inputs.branch}'`
|
||||
)
|
||||
if (inputs.signCommit) {
|
||||
await githubHelper.pushSignedCommit(
|
||||
// Stash any uncommitted tracked and untracked changes
|
||||
const stashed = await git.stashPush(['--include-untracked'])
|
||||
await git.checkout(inputs.branch)
|
||||
await githubHelper.pushSignedCommits(
|
||||
result.branchCommits,
|
||||
repoPath,
|
||||
branchRepository,
|
||||
inputs.branch,
|
||||
inputs.base,
|
||||
inputs.commitMessage,
|
||||
result.branchFileChanges
|
||||
inputs.branch
|
||||
)
|
||||
// await githubHelper.pushSignedCommit(
|
||||
// branchRepository,
|
||||
// inputs.branch,
|
||||
// inputs.base,
|
||||
// inputs.commitMessage,
|
||||
// result.branchFileChanges
|
||||
// )
|
||||
await git.checkout('-')
|
||||
if (stashed) {
|
||||
await git.stashPop()
|
||||
}
|
||||
} else {
|
||||
await git.push([
|
||||
'--force-with-lease',
|
||||
|
@ -5,6 +5,19 @@ import * as path from 'path'
|
||||
|
||||
const tagsRefSpec = '+refs/tags/*:refs/tags/*'
|
||||
|
||||
export type Commit = {
|
||||
sha: string
|
||||
tree: string
|
||||
parents: string[]
|
||||
subject: string
|
||||
body: string
|
||||
changes: {
|
||||
mode: string
|
||||
status: 'A' | 'M' | 'D'
|
||||
path: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export class GitCommandManager {
|
||||
private gitPath: string
|
||||
private workingDirectory: string
|
||||
@ -138,6 +151,43 @@ export class GitCommandManager {
|
||||
await this.exec(args)
|
||||
}
|
||||
|
||||
async getCommit(ref: string): Promise<Commit> {
|
||||
const endOfBody = '###EOB###'
|
||||
const output = await this.exec([
|
||||
'show',
|
||||
'--raw',
|
||||
'--cc',
|
||||
'--diff-filter=AMD',
|
||||
`--format=%H%n%T%n%P%n%s%n%b%n${endOfBody}`,
|
||||
ref
|
||||
])
|
||||
const lines = output.stdout.split('\n')
|
||||
const endOfBodyIndex = lines.lastIndexOf(endOfBody)
|
||||
const detailLines = lines.slice(0, endOfBodyIndex)
|
||||
|
||||
return <Commit>{
|
||||
sha: detailLines[0],
|
||||
tree: detailLines[1],
|
||||
parents: detailLines[2].split(' '),
|
||||
subject: detailLines[3],
|
||||
body: detailLines.slice(4, endOfBodyIndex).join('\n'),
|
||||
changes: lines.slice(endOfBodyIndex + 2, -1).map(line => {
|
||||
const change = line.match(
|
||||
/^:(\d{6}) (\d{6}) \w{7} \w{7} ([AMD])\s+(.*)$/
|
||||
)
|
||||
if (change) {
|
||||
return {
|
||||
mode: change[3] === 'D' ? change[1] : change[2],
|
||||
status: change[3],
|
||||
path: change[4]
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unexpected line format: ${line}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getConfigValue(configKey: string, configValue = '.'): Promise<string> {
|
||||
const output = await this.exec([
|
||||
'config',
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs} from './create-pull-request'
|
||||
import {Commit} from './git-command-manager'
|
||||
import {Octokit, OctokitOptions} from './octokit-client'
|
||||
import type {
|
||||
Repository as TempRepository,
|
||||
Ref,
|
||||
Commit,
|
||||
Commit as CommitTemp,
|
||||
FileChanges
|
||||
} from '@octokit/graphql-schema'
|
||||
import {BranchFileChanges} from './create-or-update-branch'
|
||||
@ -24,6 +25,13 @@ interface Pull {
|
||||
created: boolean
|
||||
}
|
||||
|
||||
type TreeObject = {
|
||||
path: string
|
||||
mode: '100644' | '100755' | '040000' | '160000' | '120000'
|
||||
sha: string | null
|
||||
type: 'blob'
|
||||
}
|
||||
|
||||
export class GitHubHelper {
|
||||
private octokit: InstanceType<typeof Octokit>
|
||||
|
||||
@ -192,6 +200,103 @@ export class GitHubHelper {
|
||||
return pull
|
||||
}
|
||||
|
||||
async pushSignedCommits(
|
||||
branchCommits: Commit[],
|
||||
repoPath: string,
|
||||
branchRepository: string,
|
||||
branch: string
|
||||
): Promise<void> {
|
||||
let headSha = ''
|
||||
for (const commit of branchCommits) {
|
||||
headSha = await this.createCommit(commit, repoPath, branchRepository)
|
||||
}
|
||||
await this.createOrUpdateRef(branchRepository, branch, headSha)
|
||||
}
|
||||
|
||||
private async createCommit(
|
||||
commit: Commit,
|
||||
repoPath: string,
|
||||
branchRepository: string
|
||||
): Promise<string> {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
let treeSha = commit.tree
|
||||
if (commit.changes.length > 0) {
|
||||
core.debug(`Creating tree objects for local commit ${commit.sha}`)
|
||||
const treeObjects = await Promise.all(
|
||||
commit.changes.map(async ({path, mode, status}) => {
|
||||
let sha: string | null = null
|
||||
if (status === 'A' || status === 'M') {
|
||||
core.debug(`Creating blob for file '${path}'`)
|
||||
const {data: blob} = await this.octokit.rest.git.createBlob({
|
||||
...repository,
|
||||
content: utils.readFileBase64([repoPath, path]),
|
||||
encoding: 'base64'
|
||||
})
|
||||
sha = blob.sha
|
||||
}
|
||||
return <TreeObject>{
|
||||
path,
|
||||
mode,
|
||||
sha,
|
||||
type: 'blob'
|
||||
}
|
||||
})
|
||||
)
|
||||
core.debug(`Creating tree for local commit ${commit.sha}`)
|
||||
const {data: tree} = await this.octokit.rest.git.createTree({
|
||||
...repository,
|
||||
base_tree: commit.parents[0],
|
||||
tree: treeObjects
|
||||
})
|
||||
treeSha = tree.sha
|
||||
core.debug(`Created tree ${treeSha} for local commit ${commit.sha}`)
|
||||
}
|
||||
|
||||
const {data: remoteCommit} = await this.octokit.rest.git.createCommit({
|
||||
...repository,
|
||||
parents: commit.parents,
|
||||
tree: treeSha,
|
||||
message: `${commit.subject}\n\n${commit.body}`
|
||||
})
|
||||
core.debug(
|
||||
`Created commit ${remoteCommit.sha} for local commit ${commit.sha}`
|
||||
)
|
||||
return remoteCommit.sha
|
||||
}
|
||||
|
||||
private async createOrUpdateRef(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
newHead: string
|
||||
) {
|
||||
const repository = this.parseRepository(branchRepository)
|
||||
const branchExists = await this.octokit.rest.git
|
||||
.getRef({
|
||||
...repository,
|
||||
ref: branch
|
||||
})
|
||||
.then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
|
||||
if (branchExists) {
|
||||
core.debug(`Branch ${branch} exists, updating ref`)
|
||||
await this.octokit.rest.git.updateRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `heads/${branch}`
|
||||
})
|
||||
} else {
|
||||
core.debug(`Branch ${branch} does not exist, creating ref`)
|
||||
await this.octokit.rest.git.createRef({
|
||||
...repository,
|
||||
sha: newHead,
|
||||
ref: `refs/heads/${branch}`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async pushSignedCommit(
|
||||
branchRepository: string,
|
||||
branch: string,
|
||||
@ -235,10 +340,8 @@ export class GitHubHelper {
|
||||
`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) {
|
||||
if (branchRef.repository.ref == null) {
|
||||
core.debug(`Branch does not exist - '${branch}'`)
|
||||
branchRef = await this.octokit.graphql<{repository: TempRepository}>(
|
||||
refQuery,
|
||||
@ -352,43 +455,12 @@ export class GitHubHelper {
|
||||
)
|
||||
|
||||
const commit = await this.octokit.graphql<{
|
||||
createCommitOnBranch: {ref: Ref; commit: Commit}
|
||||
createCommitOnBranch: {ref: Ref; commit: CommitTemp}
|
||||
}>(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)}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user