diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts index 4182e9b..969ff59 100644 --- a/__tests__/git.test.ts +++ b/__tests__/git.test.ts @@ -15,13 +15,16 @@ describe('Git CLI', () => { }) describe('git checkout', () => { + beforeEach(() => { + jest.spyOn(cwd, 'getWorkspace').mockReturnValue('/test-workspace') + }) + it('should create new branch', async () => { const execMock = jest.spyOn(exec, 'exec').mockResolvedValue(0) - await switchBranch('new-branch') expect(execMock).toHaveBeenCalledWith( 'git', - ['checkout', '-b', 'new-branch'], + ['-C', '/test-workspace', 'checkout', '-b', 'new-branch'], expect.objectContaining({ listeners: { stdline: expect.anything(), errline: expect.anything() }, }) @@ -89,6 +92,10 @@ describe('Git CLI', () => { }) describe('git push', () => { + beforeEach(() => { + jest.spyOn(cwd, 'getWorkspace').mockReturnValue('/test-workspace') + }) + it('should push new branch', async () => { const execMock = jest.spyOn(exec, 'exec').mockResolvedValue(0) const getInput = jest @@ -98,7 +105,15 @@ describe('Git CLI', () => { await pushCurrentBranch() expect(execMock).toHaveBeenCalledWith( 'git', - ['push', '--porcelain', '--set-upstream', 'origin', 'HEAD'], + [ + '-C', + '/test-workspace', + 'push', + '--porcelain', + '--set-upstream', + 'origin', + 'HEAD', + ], expect.objectContaining({ listeners: { stdline: expect.anything(), errline: expect.anything() }, }) @@ -113,7 +128,16 @@ describe('Git CLI', () => { expect(execMock).toHaveBeenCalled() expect(execMock).toHaveBeenCalledWith( 'git', - ['push', '--force', '--porcelain', '--set-upstream', 'origin', 'HEAD'], + [ + '-C', + '/test-workspace', + 'push', + '--force', + '--porcelain', + '--set-upstream', + 'origin', + 'HEAD', + ], expect.objectContaining({ listeners: { stdline: expect.anything(), errline: expect.anything() }, }) @@ -184,13 +208,13 @@ describe('Git CLI', () => { jest.spyOn(cwd, 'getWorkspace').mockReturnValue('/test-workspace') }) - it('should ensure file paths are within curent working directory', async () => { + it('should ensure file paths are within current working directory', async () => { const execMock = jest.spyOn(exec, 'exec').mockResolvedValue(0) await addFileChanges(['*.ts', '~/.bashrc']) expect(execMock).toHaveBeenCalledWith( 'git', - ['add', '--', '/test-workspace/*.ts', '/test-workspace/~/.bashrc'], + ['-C', '/test-workspace', 'add', '--', '*.ts', '~/.bashrc'], expect.objectContaining({ listeners: { stdline: expect.anything(), errline: expect.anything() }, }) @@ -269,6 +293,10 @@ describe('Git CLI', () => { 'A tests/run.test.ts', ] + beforeEach(() => { + jest.spyOn(cwd, 'getWorkspace').mockReturnValue('/test-workspace') + }) + it('should parse ouput into file changes', async () => { const execMock = jest .spyOn(exec, 'exec') @@ -283,7 +311,7 @@ describe('Git CLI', () => { const changes = await getFileChanges() expect(execMock).toHaveBeenCalledWith( 'git', - ['status', '-suno', '--porcelain'], + ['-C', '/test-workspace', 'status', '-suno', '--porcelain'], expect.objectContaining({ listeners: { stdline: expect.anything(), errline: expect.anything() }, }) diff --git a/action.yml b/action.yml index d3c9df1..690b6d8 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,14 @@ inputs: description: | Commit message for the file changes. required: false + owner: + description: | + GitHub repository owner (user or organization), defaults to the repo invoking the action. + required: false + repo: + description: | + GitHub repository name, defaults to the repo invoking the action. + required: false branch-name: description: | Branch to commit to. Default: Workflow triggered branch. diff --git a/dist/index.js b/dist/index.js index 258dd67..ca94b1b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -29857,10 +29857,22 @@ const base64_encoder_1 = __importDefault(__nccwpck_require__(5564)); class Blob { constructor(path) { const cwd = (0, cwd_1.getCwd)(); - this.absolutePath = path.startsWith(cwd) ? path : (0, node_path_1.join)(cwd, path); - this.path = path.startsWith(cwd) - ? path.replace(new RegExp(cwd, 'g'), '') - : path; + const workspace = (0, cwd_1.getWorkspace)(); + if (cwd === workspace || cwd.includes(workspace)) { + this.absolutePath = path.startsWith(cwd) ? path : (0, node_path_1.join)(cwd, path); + this.path = path.startsWith(cwd) + ? path.replace(new RegExp(cwd, 'g'), '') + : path; + } + else { + this.absolutePath = (0, node_path_1.join)(cwd, workspace, path); + this.path = path.startsWith(workspace) + ? path.replace(new RegExp(workspace, 'g'), '') + : path; + } + core.debug('Blob.constructor() - this.absolutePath: ' + + JSON.stringify(this.absolutePath)); + core.debug('Blob.constructor() - this.path: ' + JSON.stringify(this.path)); } get streamable() { if (!fs.existsSync(this.absolutePath)) { @@ -29879,8 +29891,9 @@ class Blob { chunks.push(chunk); else if (typeof chunk === 'string') chunks.push(node_buffer_1.Buffer.from(chunk)); + core.debug( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - core.debug(`received blob: ${chunk}`); + `Blob.load() - filepath ${this.absolutePath}, received blob: ${chunk}`); }); stream.on('error', (err) => { throw new Error(`Read file failed, error: ${err.message}, path: ${this.absolutePath}`); @@ -29982,14 +29995,21 @@ exports.addFileChanges = addFileChanges; exports.getFileChanges = getFileChanges; const core = __importStar(__nccwpck_require__(7484)); const exec_1 = __nccwpck_require__(5236); -const node_path_1 = __nccwpck_require__(6760); const cwd_1 = __nccwpck_require__(9827); function execGit(args) { return __awaiter(this, void 0, void 0, function* () { const debugOutput = []; const warningOutput = []; const errorOutput = []; - yield (0, exec_1.exec)('git', args, { + // Handle workspace + const workspace = (0, cwd_1.getWorkspace)(); + const defaultArgs = ['-C', workspace]; + core.debug('execGit() - Adding GHA parameter "workspace" to git cli args'); + core.debug('execGit() - defaultArgs: ' + JSON.stringify(defaultArgs)); + core.debug('execGit() - args parameter: ' + JSON.stringify(args)); + const mergedArgs = defaultArgs.concat(args); + core.debug('execGit() - mergedArgs: ' + JSON.stringify(mergedArgs)); + yield (0, exec_1.exec)('git', mergedArgs, { silent: true, ignoreReturnCode: true, listeners: { @@ -30033,9 +30053,7 @@ function pushCurrentBranch() { } function addFileChanges(globPatterns) { return __awaiter(this, void 0, void 0, function* () { - const workspace = (0, cwd_1.getWorkspace)(); - const workspacePaths = globPatterns.map((p) => (0, node_path_1.join)(workspace, p)); - yield execGit(['add', '--', ...workspacePaths]); + yield execGit(['add', '--', ...globPatterns]); }); } function processFileChanges(output) { @@ -30387,28 +30405,44 @@ function run() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f; try { + core.info('Getting info from GH Worklfow context'); const { owner, repo, branch } = (0, repo_1.getContext)(); + core.info('Setting variables according to inputs and context'); + core.debug('* branch'); const inputBranch = (0, input_1.getInput)('branch-name'); - if (inputBranch && inputBranch !== branch) { - yield (0, git_1.switchBranch)(inputBranch); + const selectedBranch = inputBranch ? inputBranch : branch; + core.debug('* owner'); + const inputOwner = (0, input_1.getInput)('owner'); + const selectedOwner = inputOwner ? inputOwner : owner; + core.debug('* repo'); + const inputRepo = (0, input_1.getInput)('repo'); + const selectedRepo = inputRepo ? inputRepo : repo; + if (selectedOwner == owner && + selectedRepo == repo && + selectedBranch !== branch) { + core.notice('Pushing local and current branch to remote before proceeding'); + // Git commands + yield (0, git_1.switchBranch)(selectedBranch); yield (0, git_1.pushCurrentBranch)(); } - const currentBranch = inputBranch ? inputBranch : branch; - const repository = yield core.group(`fetching repository info for owner: ${owner}, repo: ${repo}, branch: ${currentBranch}`, () => __awaiter(this, void 0, void 0, function* () { + const repository = yield core.group(`fetching repository info for owner: ${selectedOwner}, repo: ${selectedRepo}, branch: ${selectedBranch}`, () => __awaiter(this, void 0, void 0, function* () { const startTime = Date.now(); - const repositoryData = yield (0, graphql_1.getRepository)(owner, repo, currentBranch); + const repositoryData = yield (0, graphql_1.getRepository)(selectedOwner, selectedRepo, selectedBranch); const endTime = Date.now(); core.debug(`time taken: ${(endTime - startTime).toString()} ms`); return repositoryData; })); + core.info('Checking remote branches'); if (!repository.ref) { - if (inputBranch && currentBranch == inputBranch) { + if (inputBranch) { throw new errors_1.InputBranchNotFound(inputBranch); } else { - throw new errors_1.BranchNotFound(currentBranch); + throw new errors_1.BranchNotFound(branch); } } + core.info('Processing to create signed commit'); + core.debug('Get last (current?) commit'); const currentCommit = (_b = (_a = repository.ref.target.history) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b[0]; if (!currentCommit) { throw new errors_1.BranchCommitNotFound(repository.ref.name); @@ -30416,11 +30450,16 @@ function run() { let createdCommit; const filePaths = core.getMultilineInput('files'); if (filePaths.length <= 0) { - core.debug('skip file commit, empty files input'); + core.warning('skip file commit, empty files input'); } else { - core.debug(`proceed with file commit, input: ${JSON.stringify(filePaths)}`); + core.info(`Proceed with file commit, input: ${JSON.stringify(filePaths)}`); + core.info('Adding files to git index according to "filePaths"'); yield (0, git_1.addFileChanges)(filePaths); + core.info('Getting changed files'); + // NOTE: getFileChanges() returns a map of FileChanges (from '@octokit/graphql-schema'). + // The 'contents' property of each FileChange is hard coded to an empty string in processFileChanges(). + // This is expected. const fileChanges = yield (0, git_1.getFileChanges)(); const fileCount = ((_d = (_c = fileChanges.additions) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0) + ((_f = (_e = fileChanges.deletions) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0); @@ -30441,7 +30480,7 @@ function run() { const startTime = Date.now(); const commitData = yield (0, graphql_1.createCommitOnBranch)(currentCommit, commitMessage, { repositoryNameWithOwner: repository.nameWithOwner, - branchName: currentBranch, + branchName: selectedBranch, }, fileChanges); const endTime = Date.now(); core.debug(`time taken: ${(endTime - startTime).toString()} ms`); @@ -30457,11 +30496,11 @@ function run() { } const tag = (0, input_1.getInput)('tag'); if (!tag) { - core.debug('skip commit tagging, empty tag input'); + core.notice('skip commit tagging, empty tag input'); } else { const tagCommit = createdCommit !== null && createdCommit !== void 0 ? createdCommit : currentCommit; - core.debug(`proceed with commit tagging, input: ${tag}, commit: ${tagCommit.oid}`); + core.info(`proceed with commit tagging, input: ${tag}, commit: ${tagCommit.oid}`); const tagResponse = yield core.group('tagging commit', () => __awaiter(this, void 0, void 0, function* () { const startTime = Date.now(); const tagData = yield (0, graphql_1.createTagOnCommit)(tagCommit, tag, repository.id); diff --git a/src/blob.ts b/src/blob.ts index b6d598b..3d460b9 100644 --- a/src/blob.ts +++ b/src/blob.ts @@ -6,19 +6,35 @@ import { Readable } from 'node:stream' import { finished } from 'node:stream/promises' import { FileAddition } from '@octokit/graphql-schema' -import { getCwd } from './utils/cwd' +import { getCwd, getWorkspace } from './utils/cwd' import Base64Encoder from './stream/base64-encoder' export class Blob { - path: string + // Used for content access absolutePath: string + // Returned as a property of FileChange object + path: string constructor(path: string) { const cwd = getCwd() - this.absolutePath = path.startsWith(cwd) ? path : join(cwd, path) - this.path = path.startsWith(cwd) - ? path.replace(new RegExp(cwd, 'g'), '') - : path + const workspace = getWorkspace() + + if (cwd === workspace || cwd.includes(workspace)) { + this.absolutePath = path.startsWith(cwd) ? path : join(cwd, path) + this.path = path.startsWith(cwd) + ? path.replace(new RegExp(cwd, 'g'), '') + : path + } else { + this.absolutePath = join(cwd, workspace, path) + this.path = path.startsWith(workspace) + ? path.replace(new RegExp(workspace, 'g'), '') + : path + } + core.debug( + 'Blob.constructor() - this.absolutePath: ' + + JSON.stringify(this.absolutePath) + ) + core.debug('Blob.constructor() - this.path: ' + JSON.stringify(this.path)) } get streamable(): Readable { @@ -39,8 +55,10 @@ export class Blob { if (Buffer.isBuffer(chunk)) chunks.push(chunk) else if (typeof chunk === 'string') chunks.push(Buffer.from(chunk)) - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - core.debug(`received blob: ${chunk}`) + core.debug( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `Blob.load() - filepath ${this.absolutePath}, received blob: ${chunk}` + ) }) stream.on('error', (err) => { diff --git a/src/git.ts b/src/git.ts index 64f2efa..4480014 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,6 +1,5 @@ import * as core from '@actions/core' import { exec } from '@actions/exec' -import { join } from 'node:path' import { FileChanges, FileAddition, @@ -14,7 +13,18 @@ async function execGit(args: string[]) { const warningOutput: string[] = [] const errorOutput: string[] = [] - await exec('git', args, { + // Handle workspace + const workspace = getWorkspace() + const defaultArgs: string[] = ['-C', workspace] + core.debug('execGit() - Adding GHA parameter "workspace" to git cli args') + + core.debug('execGit() - defaultArgs: ' + JSON.stringify(defaultArgs)) + core.debug('execGit() - args parameter: ' + JSON.stringify(args)) + + const mergedArgs = defaultArgs.concat(args) + core.debug('execGit() - mergedArgs: ' + JSON.stringify(mergedArgs)) + + await exec('git', mergedArgs, { silent: true, ignoreReturnCode: true, listeners: { @@ -53,10 +63,7 @@ export async function pushCurrentBranch() { } export async function addFileChanges(globPatterns: string[]) { - const workspace = getWorkspace() - const workspacePaths = globPatterns.map((p) => join(workspace, p)) - - await execGit(['add', '--', ...workspacePaths]) + await execGit(['add', '--', ...globPatterns]) } function processFileChanges(output: string[]) { diff --git a/src/github/graphql.ts b/src/github/graphql.ts index 9d93147..e363d82 100644 --- a/src/github/graphql.ts +++ b/src/github/graphql.ts @@ -12,8 +12,8 @@ import { } from '@octokit/graphql-schema' import { graphqlClient } from './client' +import { RepositoryWithCommitHistory } from './types' import { getBlob } from '../blob' -import { RepositoryWithCommitHistory } from '../github/types' function formatLogMessage(...params: Record[]): string { return Object.entries(Object.assign({}, ...params) as Record) diff --git a/src/main.ts b/src/main.ts index 4014982..34da28a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -23,32 +23,61 @@ import { export async function run(): Promise { try { + core.info('Getting info from GH Worklfow context') const { owner, repo, branch } = getContext() + + core.info('Setting variables according to inputs and context') + core.debug('* branch') const inputBranch = getInput('branch-name') - if (inputBranch && inputBranch !== branch) { - await switchBranch(inputBranch) + const selectedBranch = inputBranch ? inputBranch : branch + + core.debug('* owner') + const inputOwner = getInput('owner') + const selectedOwner = inputOwner ? inputOwner : owner + + core.debug('* repo') + const inputRepo = getInput('repo') + const selectedRepo = inputRepo ? inputRepo : repo + + if ( + selectedOwner == owner && + selectedRepo == repo && + selectedBranch !== branch + ) { + core.notice( + 'Pushing local and current branch to remote before proceeding' + ) + // Git commands + await switchBranch(selectedBranch) await pushCurrentBranch() } - const currentBranch = inputBranch ? inputBranch : branch + const repository = await core.group( - `fetching repository info for owner: ${owner}, repo: ${repo}, branch: ${currentBranch}`, + `fetching repository info for owner: ${selectedOwner}, repo: ${selectedRepo}, branch: ${selectedBranch}`, async () => { const startTime = Date.now() - const repositoryData = await getRepository(owner, repo, currentBranch) + const repositoryData = await getRepository( + selectedOwner, + selectedRepo, + selectedBranch + ) const endTime = Date.now() core.debug(`time taken: ${(endTime - startTime).toString()} ms`) return repositoryData } ) + core.info('Checking remote branches') if (!repository.ref) { - if (inputBranch && currentBranch == inputBranch) { + if (inputBranch) { throw new InputBranchNotFound(inputBranch) } else { - throw new BranchNotFound(currentBranch) + throw new BranchNotFound(branch) } } + core.info('Processing to create signed commit') + core.debug('Get last (current?) commit') const currentCommit = repository.ref.target.history?.nodes?.[0] if (!currentCommit) { throw new BranchCommitNotFound(repository.ref.name) @@ -57,13 +86,17 @@ export async function run(): Promise { let createdCommit: Commit | undefined const filePaths = core.getMultilineInput('files') if (filePaths.length <= 0) { - core.debug('skip file commit, empty files input') + core.warning('skip file commit, empty files input') } else { - core.debug( - `proceed with file commit, input: ${JSON.stringify(filePaths)}` - ) + core.info(`Proceed with file commit, input: ${JSON.stringify(filePaths)}`) + core.info('Adding files to git index according to "filePaths"') await addFileChanges(filePaths) + + core.info('Getting changed files') + // NOTE: getFileChanges() returns a map of FileChanges (from '@octokit/graphql-schema'). + // The 'contents' property of each FileChange is hard coded to an empty string in processFileChanges(). + // This is expected. const fileChanges = await getFileChanges() const fileCount = (fileChanges.additions?.length ?? 0) + @@ -89,7 +122,7 @@ export async function run(): Promise { commitMessage, { repositoryNameWithOwner: repository.nameWithOwner, - branchName: currentBranch, + branchName: selectedBranch, }, fileChanges ) @@ -109,10 +142,10 @@ export async function run(): Promise { const tag = getInput('tag') if (!tag) { - core.debug('skip commit tagging, empty tag input') + core.notice('skip commit tagging, empty tag input') } else { const tagCommit = createdCommit ?? currentCommit - core.debug( + core.info( `proceed with commit tagging, input: ${tag}, commit: ${tagCommit.oid as string}` ) const tagResponse = await core.group('tagging commit', async () => {