/** * Copyright 2023 actions-toolkit authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as core from '@actions/core'; import {Octokit} from '@octokit/core'; import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'; import {Exec} from './exec'; import {GitHub} from './github'; import {Context} from '@actions/github/lib/context'; import {Context as GitContext} from './types/git'; export class Git { public static async context(): Promise { const ctx = new Context(); ctx.ref = await Git.ref(); ctx.sha = await Git.fullCommit(); return ctx; } public static async isInsideWorkTree(): Promise { return await Git.exec(['rev-parse', '--is-inside-work-tree']) .then(out => { return out === 'true'; }) .catch(() => { return false; }); } public static async remoteSha(repo: string, ref: string, token?: string): Promise { const repoMatch = repo.match(/github.com\/([^/]+)\/([^/]+?)(?:\.git)?(\/|$)/); // if we have a token and this is a GitHub repo we can use the GitHub API if (token && repoMatch) { core.setSecret(token); const octokit = new (Octokit.plugin(restEndpointMethods).defaults({ baseUrl: GitHub.apiURL }))({auth: token}); const [owner, repoName] = repoMatch.slice(1, 3); try { return ( await octokit.rest.repos.listCommits({ owner: owner, repo: repoName, sha: ref, per_page: 1 }) ).data[0].sha; } catch (e) { throw new Error(`Cannot find remote ref for ${repo}#${ref}: ${e.message}`); } } // otherwise we fall back to git ls-remote return await Git.exec(['ls-remote', repo, ref]).then(out => { const [rsha] = out.split(/[\s\t]/); if (rsha.length == 0) { throw new Error(`Cannot find remote ref for ${repo}#${ref}`); } return rsha; }); } public static async remoteURL(): Promise { return await Git.exec(['remote', 'get-url', 'origin']).then(rurl => { if (rurl.length == 0) { return Git.exec(['remote', 'get-url', 'upstream']).then(rurl => { if (rurl.length == 0) { throw new Error(`Cannot find remote URL for origin or upstream`); } return rurl; }); } return rurl; }); } public static async ref(): Promise { return await Git.exec(['symbolic-ref', 'HEAD']).catch(() => { // if it fails (for example in a detached HEAD state), falls back to // using git tag or describe to get the exact matching tag name. return Git.tag().then(tag => { return `refs/tags/${tag}`; }); }); } public static async fullCommit(): Promise { return await Git.exec(['show', '--format=%H', 'HEAD', '--quiet', '--']); } public static async shortCommit(): Promise { return await Git.exec(['show', '--format=%h', 'HEAD', '--quiet', '--']); } public static async tag(): Promise { return await Git.exec(['tag', '--points-at', 'HEAD', '--sort', '-version:creatordate']).then(tags => { if (tags.length == 0) { return Git.exec(['describe', '--tags', '--abbrev=0']); } return tags.split('\n')[0]; }); } private static async exec(args: string[] = []): Promise { return await Exec.getExecOutput(`git`, args, { ignoreReturnCode: true, silent: true }).then(res => { if (res.stderr.length > 0 && res.exitCode != 0) { throw new Error(res.stderr); } return res.stdout.trim(); }); } }