@docker/actions-toolkit
Version:
Toolkit for Docker (GitHub) Actions
185 lines • 7.1 kB
JavaScript
/**
* 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 * as github from '@actions/github';
import { Exec } from './exec.js';
import { GitHub } from './github/github.js';
export class Git {
static async context() {
const ctx = { ...github.context };
ctx.ref = await Git.ref();
ctx.sha = await Git.fullCommit();
return ctx;
}
static async isInsideWorkTree() {
return await Git.exec(['rev-parse', '--is-inside-work-tree'])
.then(out => {
return out === 'true';
})
.catch(() => {
return false;
});
}
static async remoteSha(repo, ref, token) {
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 = github.getOctokit(token, {
baseUrl: GitHub.apiURL
});
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;
});
}
static async remoteURL() {
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;
});
}
static async ref() {
const isHeadDetached = await Git.isHeadDetached();
if (isHeadDetached) {
return await Git.getDetachedRef();
}
return await Git.exec(['symbolic-ref', 'HEAD']);
}
static async fullCommit() {
return await Git.exec(['show', '--format=%H', 'HEAD', '--quiet', '--']);
}
static async shortCommit() {
return await Git.exec(['show', '--format=%h', 'HEAD', '--quiet', '--']);
}
static async tag() {
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];
});
}
static async isHeadDetached() {
return await Git.exec(['branch', '--show-current']).then(res => {
return res.length == 0;
});
}
static async getDetachedRef() {
const res = await Git.exec(['show', '-s', '--pretty=%D']);
core.debug(`detached HEAD ref: ${res}`);
const normalizedRef = res.replace(/^grafted, /, '').trim();
if (normalizedRef === 'HEAD') {
return await Git.inferRefFromHead();
}
// Can be "HEAD, <tagname>" or "grafted, HEAD, <tagname>"
const refMatch = normalizedRef.match(/^HEAD, (.*)$/);
if (!refMatch || !refMatch[1]) {
throw new Error(`Cannot find detached HEAD ref in "${res}"`);
}
const ref = refMatch[1].trim();
// Tag refs are formatted as "tag: <tagname>"
if (ref.startsWith('tag: ')) {
return `refs/tags/${ref.split(':')[1].trim()}`;
}
// Pull request merge refs are formatted as "pull/<number>/<state>"
const prMatch = ref.match(/^pull\/\d+\/(head|merge)$/);
if (prMatch) {
return `refs/${ref}`;
}
// Branch refs can be formatted as "<origin>/<branch-name>, <branch-name>"
const branchMatch = ref.match(/^[^/]+\/[^/]+, (.+)$/);
if (branchMatch) {
return `refs/heads/${branchMatch[1].trim()}`;
}
// Branch refs checked out by its latest SHA can be formatted as "<origin>/<branch-name>"
const shaBranchMatch = ref.match(/^[^/]+\/(.+)$/);
if (shaBranchMatch) {
return `refs/heads/${shaBranchMatch[1].trim()}`;
}
throw new Error(`Unsupported detached HEAD ref in "${res}"`);
}
static async exec(args = []) {
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();
});
}
static async inferRefFromHead() {
const localRef = await Git.findContainingRef('refs/heads/');
if (localRef) {
return localRef;
}
const remoteRef = await Git.findContainingRef('refs/remotes/');
if (remoteRef) {
const remoteMatch = remoteRef.match(/^refs\/remotes\/[^/]+\/(.+)$/);
if (remoteMatch) {
return `refs/heads/${remoteMatch[1]}`;
}
return remoteRef;
}
const tagRef = await Git.exec(['tag', '--contains', 'HEAD']);
const [firstTag] = tagRef
.split('\n')
.map(tag => tag.trim())
.filter(tag => tag.length > 0);
if (firstTag) {
return `refs/tags/${firstTag}`;
}
throw new Error(`Cannot infer ref from detached HEAD`);
}
static async findContainingRef(scope) {
const refs = await Git.exec(['for-each-ref', '--format=%(refname)', '--contains', 'HEAD', '--sort=-committerdate', scope]);
const [first] = refs
.split('\n')
.map(r => r.trim())
.filter(r => r.length > 0);
return first;
}
static async commitDate(ref) {
return new Date(await Git.exec(['show', '-s', '--format="%ci"', ref]));
}
}
//# sourceMappingURL=git.js.map