Skip to content

Commit 442a46c

Browse files
committed
git: alternative github context and additional methods
Signed-off-by: CrazyMax <[email protected]>
1 parent 2e59ae7 commit 442a46c

File tree

5 files changed

+187
-16
lines changed

5 files changed

+187
-16
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
-
3535
name: Checkout
3636
uses: actions/checkout@v3
37+
with:
38+
fetch-depth: 0
3739
-
3840
name: Test
3941
uses: docker/bake-action@v2

__tests__/git.test.ts

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,107 @@
1717
import {beforeEach, describe, expect, it, jest} from '@jest/globals';
1818

1919
import {Git} from '../src/git';
20+
import {Exec} from '../src/exec';
21+
import {ExecOutput} from '@actions/exec';
2022

2123
beforeEach(() => {
2224
jest.clearAllMocks();
25+
jest.restoreAllMocks();
2326
});
2427

25-
describe('git', () => {
26-
it('returns git remote ref', async () => {
27-
try {
28-
expect(await Git.getRemoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head')).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
29-
} catch (e) {
30-
// eslint-disable-next-line jest/no-conditional-expect
31-
expect(e).toEqual(null);
32-
}
28+
describe('context', () => {
29+
it('returns mocked ref and sha', async () => {
30+
jest.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
31+
const fullCmd = `${cmd} ${args?.join(' ')}`;
32+
let result = '';
33+
switch (fullCmd) {
34+
case 'git show --format=%H HEAD --quiet --':
35+
result = 'test-sha';
36+
break;
37+
case 'git symbolic-ref HEAD':
38+
result = 'refs/heads/test';
39+
break;
40+
}
41+
return Promise.resolve({
42+
stdout: result,
43+
stderr: '',
44+
exitCode: 0
45+
});
46+
});
47+
const ctx = await Git.context();
48+
expect(ctx.ref).toEqual('refs/heads/test');
49+
expect(ctx.sha).toEqual('test-sha');
50+
});
51+
});
52+
53+
describe('isInsideWorkTree', () => {
54+
it('have been called', async () => {
55+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
56+
await Git.isInsideWorkTree();
57+
expect(execSpy).toHaveBeenCalledWith(`git`, ['rev-parse', '--is-inside-work-tree'], {
58+
silent: true,
59+
ignoreReturnCode: true
60+
});
61+
});
62+
});
63+
64+
describe('remoteSha', () => {
65+
it('returns git remote sha', async () => {
66+
expect(await Git.remoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head')).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
67+
});
68+
});
69+
70+
describe('remoteURL', () => {
71+
it('have been called', async () => {
72+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
73+
await Git.remoteURL();
74+
expect(execSpy).toHaveBeenCalledWith(`git`, ['remote', 'get-url', 'origin'], {
75+
silent: true,
76+
ignoreReturnCode: true
77+
});
78+
});
79+
});
80+
81+
describe('ref', () => {
82+
it('have been called', async () => {
83+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
84+
await Git.ref();
85+
expect(execSpy).toHaveBeenCalledWith(`git`, ['symbolic-ref', 'HEAD'], {
86+
silent: true,
87+
ignoreReturnCode: true
88+
});
89+
});
90+
});
91+
92+
describe('fullCommit', () => {
93+
it('have been called', async () => {
94+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
95+
await Git.fullCommit();
96+
expect(execSpy).toHaveBeenCalledWith(`git`, ['show', '--format=%H', 'HEAD', '--quiet', '--'], {
97+
silent: true,
98+
ignoreReturnCode: true
99+
});
100+
});
101+
});
102+
103+
describe('shortCommit', () => {
104+
it('have been called', async () => {
105+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
106+
await Git.shortCommit();
107+
expect(execSpy).toHaveBeenCalledWith(`git`, ['show', '--format=%h', 'HEAD', '--quiet', '--'], {
108+
silent: true,
109+
ignoreReturnCode: true
110+
});
111+
});
112+
});
113+
114+
describe('tag', () => {
115+
it('have been called', async () => {
116+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
117+
await Git.tag();
118+
expect(execSpy).toHaveBeenCalledWith(`git`, ['tag', '--points-at', 'HEAD', '--sort', '-version:creatordate'], {
119+
silent: true,
120+
ignoreReturnCode: true
121+
});
33122
});
34123
});

src/buildx/install.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class Install {
7373
if (ref.match(/^[0-9a-fA-F]{40}$/)) {
7474
vspec = ref;
7575
} else {
76-
vspec = await Git.getRemoteSha(repo, ref);
76+
vspec = await Git.remoteSha(repo, ref);
7777
}
7878
core.debug(`Install.build: tool version spec ${vspec}`);
7979

src/git.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,83 @@
1414
* limitations under the License.
1515
*/
1616

17+
import * as github from '@actions/github';
18+
1719
import {Exec} from './exec';
20+
import {Context} from './types/git';
1821

1922
export class Git {
20-
public static async getRemoteSha(repo: string, ref: string): Promise<string> {
21-
return await Exec.getExecOutput(`git`, ['ls-remote', repo, ref], {
23+
public static async context(): Promise<Context> {
24+
const ctx = github.context;
25+
ctx.ref = await Git.ref();
26+
ctx.sha = await Git.fullCommit();
27+
return ctx;
28+
}
29+
30+
public static async isInsideWorkTree(): Promise<boolean> {
31+
return await Git.exec(['rev-parse', '--is-inside-work-tree'])
32+
.then(out => {
33+
return out === 'true';
34+
})
35+
.catch(() => {
36+
return false;
37+
});
38+
}
39+
40+
public static async remoteSha(repo: string, ref: string): Promise<string> {
41+
return await Git.exec(['ls-remote', repo, ref]).then(out => {
42+
const [rsha] = out.split(/[\s\t]/);
43+
if (rsha.length == 0) {
44+
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
45+
}
46+
return rsha;
47+
});
48+
}
49+
50+
public static async remoteURL(): Promise<string> {
51+
return await Git.exec(['remote', 'get-url', 'origin']).then(rurl => {
52+
if (rurl.length == 0) {
53+
return Git.exec(['remote', 'get-url', 'upstream']).then(rurl => {
54+
if (rurl.length == 0) {
55+
throw new Error(`Cannot find remote URL for origin or upstream`);
56+
}
57+
return rurl;
58+
});
59+
}
60+
return rurl;
61+
});
62+
}
63+
64+
public static async ref(): Promise<string> {
65+
return await Git.exec(['symbolic-ref', 'HEAD']);
66+
}
67+
68+
public static async fullCommit(): Promise<string> {
69+
return await Git.exec(['show', '--format=%H', 'HEAD', '--quiet', '--']);
70+
}
71+
72+
public static async shortCommit(): Promise<string> {
73+
return await Git.exec(['show', '--format=%h', 'HEAD', '--quiet', '--']);
74+
}
75+
76+
public static async tag(): Promise<string> {
77+
return await Git.exec(['tag', '--points-at', 'HEAD', '--sort', '-version:creatordate']).then(tags => {
78+
if (tags.length == 0) {
79+
return Git.exec(['describe', '--tags', '--abbrev=0']);
80+
}
81+
return tags.split('\n')[0];
82+
});
83+
}
84+
85+
private static async exec(args: string[] = []): Promise<string> {
86+
return await Exec.getExecOutput(`git`, args, {
2287
ignoreReturnCode: true,
2388
silent: true
2489
}).then(res => {
2590
if (res.stderr.length > 0 && res.exitCode != 0) {
2691
throw new Error(res.stderr);
2792
}
28-
const [rsha] = res.stdout.trim().split(/[\s\t]/);
29-
if (rsha.length == 0) {
30-
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
31-
}
32-
return rsha;
93+
return res.stdout.trim();
3394
});
3495
}
3596
}

src/types/git.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright 2023 actions-toolkit authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {Context as GitHubContext} from '@actions/github/lib/context';
18+
19+
export type Context = GitHubContext;

0 commit comments

Comments
 (0)