Skip to content

Commit b9edf7d

Browse files
authored
Merge pull request #1188 from lucacome/fix/git-context
git: correctly extract tag name when commit has multiple decorations
2 parents 416c529 + 1a3f294 commit b9edf7d

2 files changed

Lines changed: 104 additions & 1 deletion

File tree

__tests__/git.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ describe('ref', () => {
122122
case 'git show -s --pretty=%D':
123123
result = 'HEAD, tag: 8.0.0';
124124
break;
125+
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
126+
result = 'refs/tags/8.0.0';
127+
break;
125128
}
126129
return Promise.resolve({
127130
stdout: result,
@@ -144,6 +147,9 @@ describe('ref', () => {
144147
case 'git show -s --pretty=%D':
145148
result = 'grafted, HEAD, tag: 8.0.0';
146149
break;
150+
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
151+
result = 'refs/tags/8.0.0';
152+
break;
147153
}
148154
return Promise.resolve({
149155
stdout: result,
@@ -408,6 +414,81 @@ describe('ref', () => {
408414
const ref = await Git.ref();
409415
expect(ref).toEqual('refs/remotes/unusual-format');
410416
});
417+
418+
it('returns mocked detached tag ref when commit also has branch decorations', async () => {
419+
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
420+
const fullCmd = `${cmd} ${args?.join(' ')}`;
421+
let result = '';
422+
switch (fullCmd) {
423+
case 'git branch --show-current':
424+
result = '';
425+
break;
426+
case 'git show -s --pretty=%D':
427+
result = 'HEAD, tag: v8.0.0, origin/release-branch';
428+
break;
429+
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
430+
result = 'refs/tags/v8.0.0';
431+
break;
432+
}
433+
return Promise.resolve({
434+
stdout: result,
435+
stderr: '',
436+
exitCode: 0
437+
});
438+
});
439+
const ref = await Git.ref();
440+
expect(ref).toEqual('refs/tags/v8.0.0');
441+
});
442+
443+
it('returns mocked detached tag ref (shallow clone) when commit also has branch decorations', async () => {
444+
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
445+
const fullCmd = `${cmd} ${args?.join(' ')}`;
446+
let result = '';
447+
switch (fullCmd) {
448+
case 'git branch --show-current':
449+
result = '';
450+
break;
451+
case 'git show -s --pretty=%D':
452+
result = 'grafted, HEAD, tag: v8.0.0, origin/release-branch';
453+
break;
454+
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
455+
result = 'refs/tags/v8.0.0';
456+
break;
457+
}
458+
return Promise.resolve({
459+
stdout: result,
460+
stderr: '',
461+
exitCode: 0
462+
});
463+
});
464+
const ref = await Git.ref();
465+
expect(ref).toEqual('refs/tags/v8.0.0');
466+
});
467+
468+
it('returns mocked detached tag ref when tag name contains a comma', async () => {
469+
vi.spyOn(Exec, 'getExecOutput').mockImplementation((cmd, args): Promise<ExecOutput> => {
470+
const fullCmd = `${cmd} ${args?.join(' ')}`;
471+
let result = '';
472+
switch (fullCmd) {
473+
case 'git branch --show-current':
474+
result = '';
475+
break;
476+
case 'git show -s --pretty=%D':
477+
result = 'HEAD, tag: release,with-comma, origin/release-branch';
478+
break;
479+
case 'git for-each-ref --format=%(refname) --points-at HEAD refs/tags/':
480+
result = 'refs/tags/release,with-comma';
481+
break;
482+
}
483+
return Promise.resolve({
484+
stdout: result,
485+
stderr: '',
486+
exitCode: 0
487+
});
488+
});
489+
const ref = await Git.ref();
490+
expect(ref).toEqual('refs/tags/release,with-comma');
491+
});
411492
});
412493

413494
describe('fullCommit', () => {

src/git.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export class Git {
138138

139139
// Tag refs are formatted as "tag: <tagname>"
140140
if (ref.startsWith('tag: ')) {
141-
return `refs/tags/${ref.split(':')[1].trim()}`;
141+
return await Git.findDetachedTagRef(ref, res);
142142
}
143143

144144
// Pull request merge refs are formatted as "pull/<number>/<state>"
@@ -201,6 +201,28 @@ export class Git {
201201
throw new Error(`Cannot infer ref from detached HEAD`);
202202
}
203203

204+
private static async findDetachedTagRef(tagDecoration: string, originalRef: string): Promise<string> {
205+
const tagRefs = await Git.exec(['for-each-ref', '--format=%(refname)', '--points-at', 'HEAD', 'refs/tags/']);
206+
const refs = tagRefs
207+
.split('\n')
208+
.map(tagRef => tagRef.trim())
209+
.filter(tagRef => tagRef.length > 0)
210+
.sort((a, b) => b.length - a.length);
211+
212+
for (const tagRef of refs) {
213+
const decoration = `tag: ${tagRef.slice('refs/tags/'.length)}`;
214+
if (tagDecoration === decoration || tagDecoration.startsWith(`${decoration}, `)) {
215+
return tagRef;
216+
}
217+
}
218+
219+
if (refs.length === 1) {
220+
return refs[0];
221+
}
222+
223+
throw new Error(`Cannot find detached tag ref in "${originalRef}"`);
224+
}
225+
204226
private static async findContainingRef(scope: string): Promise<string | undefined> {
205227
const refs = await Git.exec(['for-each-ref', '--format=%(refname)', '--contains', 'HEAD', '--sort=-committerdate', scope]);
206228

0 commit comments

Comments
 (0)