Skip to content

Commit 8a69d6c

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

File tree

4 files changed

+206
-14
lines changed

4 files changed

+206
-14
lines changed

__tests__/git.test.ts

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,131 @@
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 () => {
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+
try {
57+
await Git.isInsideWorkTree();
58+
} catch (err) {
59+
// noop
60+
}
61+
expect(execSpy).toHaveBeenCalledWith(`git`, ['rev-parse', '--is-inside-work-tree'], {
62+
silent: true,
63+
ignoreReturnCode: true
64+
});
65+
});
66+
});
67+
68+
describe('remoteSha', () => {
69+
it('returns git remote sha', async () => {
70+
expect(await Git.remoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head')).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
71+
});
72+
});
73+
74+
describe('remoteURL', () => {
75+
it('have been called', async () => {
76+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
77+
try {
78+
await Git.remoteURL();
79+
} catch (err) {
80+
// noop
81+
}
82+
expect(execSpy).toHaveBeenCalledWith(`git`, ['remote', 'get-url', 'origin'], {
83+
silent: true,
84+
ignoreReturnCode: true
85+
});
86+
});
87+
});
88+
89+
describe('ref', () => {
90+
it('have been called', async () => {
91+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
92+
try {
93+
await Git.ref();
94+
} catch (err) {
95+
// noop
96+
}
97+
expect(execSpy).toHaveBeenCalledWith(`git`, ['symbolic-ref', 'HEAD'], {
98+
silent: true,
99+
ignoreReturnCode: true
100+
});
101+
});
102+
});
103+
104+
describe('fullCommit', () => {
105+
it('have been called', async () => {
106+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
107+
try {
108+
await Git.fullCommit();
109+
} catch (err) {
110+
// noop
111+
}
112+
expect(execSpy).toHaveBeenCalledWith(`git`, ['show', '--format=%H', 'HEAD', '--quiet', '--'], {
113+
silent: true,
114+
ignoreReturnCode: true
115+
});
116+
});
117+
});
118+
119+
describe('shortCommit', () => {
120+
it('have been called', async () => {
121+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
122+
try {
123+
await Git.shortCommit();
124+
} catch (err) {
125+
// noop
126+
}
127+
expect(execSpy).toHaveBeenCalledWith(`git`, ['show', '--format=%h', 'HEAD', '--quiet', '--'], {
128+
silent: true,
129+
ignoreReturnCode: true
130+
});
131+
});
132+
});
133+
134+
describe('tag', () => {
135+
it('have been called', async () => {
136+
const execSpy = jest.spyOn(Exec, 'getExecOutput');
27137
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);
138+
await Git.tag();
139+
} catch (err) {
140+
// noop
32141
}
142+
expect(execSpy).toHaveBeenCalledWith(`git`, ['tag', '--points-at', 'HEAD', '--sort', '-version:creatordate'], {
143+
silent: true,
144+
ignoreReturnCode: true
145+
});
33146
});
34147
});

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: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,81 @@
1515
*/
1616

1717
import {Exec} from './exec';
18+
import {Context} from '@actions/github/lib/context';
19+
import {Context as GitContext} from './types/git';
1820

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

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)